feat: fetch K3s image tag from channelserver on latest/stable/+min.major (#841)

pull/843/head
Thorsten Klein 3 years ago committed by GitHub
parent a1a04b7907
commit b073e7675f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      cmd/cluster/clusterCreate.go
  2. 2
      cmd/node/nodeCreate.go
  3. 2
      go.mod
  4. 94
      go.sum
  5. 15
      pkg/config/transform.go
  6. 2
      pkg/config/v1alpha2/types.go
  7. 2
      pkg/config/v1alpha3/types.go
  8. 54
      pkg/types/k3s/channel.go
  9. 287
      vendor/github.com/docker/distribution/manifest/schema1/config_builder.go
  10. 184
      vendor/github.com/docker/distribution/manifest/schema1/manifest.go
  11. 98
      vendor/github.com/docker/distribution/manifest/schema1/reference_builder.go
  12. 68
      vendor/github.com/docker/distribution/manifest/schema1/sign.go
  13. 32
      vendor/github.com/docker/distribution/manifest/schema1/verify.go
  14. 13
      vendor/github.com/docker/libtrust/CONTRIBUTING.md
  15. 191
      vendor/github.com/docker/libtrust/LICENSE
  16. 3
      vendor/github.com/docker/libtrust/MAINTAINERS
  17. 22
      vendor/github.com/docker/libtrust/README.md
  18. 175
      vendor/github.com/docker/libtrust/certificates.go
  19. 9
      vendor/github.com/docker/libtrust/doc.go
  20. 428
      vendor/github.com/docker/libtrust/ec_key.go
  21. 50
      vendor/github.com/docker/libtrust/filter.go
  22. 56
      vendor/github.com/docker/libtrust/hash.go
  23. 657
      vendor/github.com/docker/libtrust/jsonsign.go
  24. 253
      vendor/github.com/docker/libtrust/key.go
  25. 255
      vendor/github.com/docker/libtrust/key_files.go
  26. 175
      vendor/github.com/docker/libtrust/key_manager.go
  27. 427
      vendor/github.com/docker/libtrust/rsa_key.go
  28. 363
      vendor/github.com/docker/libtrust/util.go
  29. 26
      vendor/github.com/heroku/docker-registry-client/LICENSE.md
  30. 151
      vendor/github.com/heroku/docker-registry-client/registry/authchallenge.go
  31. 23
      vendor/github.com/heroku/docker-registry-client/registry/basictransport.go
  32. 108
      vendor/github.com/heroku/docker-registry-client/registry/blob.go
  33. 46
      vendor/github.com/heroku/docker-registry-client/registry/errortransport.go
  34. 50
      vendor/github.com/heroku/docker-registry-client/registry/json.go
  35. 126
      vendor/github.com/heroku/docker-registry-client/registry/manifest.go
  36. 118
      vendor/github.com/heroku/docker-registry-client/registry/registry.go
  37. 26
      vendor/github.com/heroku/docker-registry-client/registry/repositories.go
  38. 25
      vendor/github.com/heroku/docker-registry-client/registry/tags.go
  39. 128
      vendor/github.com/heroku/docker-registry-client/registry/tokentransport.go
  40. 7
      vendor/modules.txt
  41. 54
      version/version.go
  42. 40
      version/version_test.go

@ -291,7 +291,7 @@ func NewCmdClusterCreate() *cobra.Command {
cmd.Flags().StringP("image", "i", "", "Specify k3s image that you want to use for the nodes")
_ = cfgViper.BindPFlag("image", cmd.Flags().Lookup("image"))
cfgViper.SetDefault("image", fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.GetK3sVersion(false)))
cfgViper.SetDefault("image", fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.K3sVersion))
cmd.Flags().String("network", "", "Join an existing network")
_ = cfgViper.BindPFlag("network", cmd.Flags().Lookup("network"))

@ -77,7 +77,7 @@ func NewCmdNodeCreate() *cobra.Command {
l.Log().Fatalln("Failed to register flag completion for '--cluster'", err)
}
cmd.Flags().StringP("image", "i", fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.GetK3sVersion(false)), "Specify k3s image used for the node(s)")
cmd.Flags().StringP("image", "i", fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.K3sVersion), "Specify k3s image used for the node(s)")
cmd.Flags().String("memory", "", "Memory limit imposed on the node [From docker]")
cmd.Flags().BoolVar(&createNodeOpts.Wait, "wait", true, "Wait for the node(s) to be ready before returning.")

@ -15,7 +15,6 @@ require (
github.com/docker/go-units v0.4.0
github.com/fvbommel/sortorder v1.0.2 // indirect
github.com/go-test/deep v1.0.7
github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5
github.com/imdario/mergo v0.3.12
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
github.com/mitchellh/copystructure v1.2.0
@ -50,7 +49,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-logr/logr v0.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect

@ -78,13 +78,11 @@ github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5
github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
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=
@ -265,7 +263,6 @@ github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v20.10.8+incompatible h1:/zO/6y9IOpcehE49yMRTV9ea0nBpb8OeqSskXLNfH1E=
github.com/docker/cli v20.10.8+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20171011171712-7484e51bf6af/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
@ -307,7 +304,6 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
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/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@ -321,21 +317,18 @@ github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui72
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
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 v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
@ -348,20 +341,6 @@ github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=
github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg=
github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk=
github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks=
github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
@ -386,7 +365,6 @@ github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4er
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -413,23 +391,6 @@ 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/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM=
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
github.com/golangci/golangci-lint v1.17.2-0.20190909185456-6163a8a79084/go.mod h1:jXakAOSd+FMU9dP3D6IfBK7HyD1q/RLHI9NOY8veycY=
github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU=
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
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=
@ -484,7 +445,6 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@ -511,15 +471,12 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5 h1:6ZR6HQ+P9ZUwHlYq+bU7e9wqAImxKUguq8fp2gZSgCo=
github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5/go.mod h1:Yho0S7KhsnHQRCC5lDraYF1SsLMeWtf/tKdufKu3TJA=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -554,14 +511,9 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
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=
github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -578,9 +530,7 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
@ -594,7 +544,6 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@ -609,13 +558,11 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@ -641,14 +588,11 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
@ -666,7 +610,6 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
@ -701,7 +644,6 @@ github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xA
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
@ -751,25 +693,17 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
@ -784,27 +718,22 @@ github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:s
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@ -816,7 +745,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
@ -842,19 +770,13 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
@ -914,7 +836,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -965,11 +886,9 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1043,7 +962,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1133,7 +1051,6 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@ -1149,24 +1066,17 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/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-20190121143147-24cd39ecf745/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-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@ -1423,9 +1333,6 @@ k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9 h1:imL9YgXQ9p7xmPzHFm/vVd/cF78jad+n4wK1ABwYtMM=
k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
@ -1438,4 +1345,3 @@ sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZa
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

@ -56,9 +56,18 @@ func TransformSimpleToClusterConfig(ctx context.Context, runtime runtimes.Runtim
simpleConfig.Name = k3d.DefaultClusterName
}
// fetch latest image
if simpleConfig.Image == "latest" {
simpleConfig.Image = version.GetK3sVersion(true)
/* Special cases for Image:
* - "latest" / "stable": get latest / stable channel image
* - starts with "+": get channel following the "+"
*/
if simpleConfig.Image == "latest" || simpleConfig.Image == "stable" || strings.HasPrefix(simpleConfig.Image, "+") {
searchChannel := strings.TrimPrefix(simpleConfig.Image, "+")
v, err := version.GetK3sVersion(searchChannel)
if err != nil {
return nil, err
}
l.Log().Debugf("Using fetched K3s version %s", v)
simpleConfig.Image = fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, v)
}
clusterNetwork := k3d.ClusterNetwork{}

@ -53,7 +53,7 @@ var DefaultConfig = fmt.Sprintf(
DefaultConfigTpl,
ApiVersion,
k3d.DefaultClusterName,
fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.GetK3sVersion(false)),
fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.K3sVersion),
)
type VolumeWithNodeFilters struct {

@ -53,7 +53,7 @@ image: %s
var DefaultConfig = fmt.Sprintf(
DefaultConfigTpl,
k3d.DefaultClusterName,
fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.GetK3sVersion(false)),
fmt.Sprintf("%s:%s", k3d.DefaultK3sImageRepo, version.K3sVersion),
)
type VolumeWithNodeFilters struct {

@ -0,0 +1,54 @@
/*
Copyright © 2020-2021 The k3d Author(s)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package k3s
const K3sChannelServerURL string = "https://update.k3s.io/v1-release/channels"
// Source: https://github.com/rancher/channelserver/blob/b084228bfc14d280e524ad132311d8cfcebd1271/pkg/model/config.go#L15-L20
type Channel struct {
Name string `json:"name,omitempty"`
Latest string `json:"latest,omitempty"`
LatestRegexp string `json:"latestRegexp,omitempty"`
ExcludeRegexp string `json:"excludeRegexp,omitempty"`
}
type ChannelServerResponse struct {
Channels []struct {
Channel `json:,squash`
} `json:"data"`
}
/*
{
"type": "collection",
"links": {
"self": "…/v1-release/channels"
},
"actions": { },
"resourceType": "channels",
"data": [ 13 items
{
"id": "stable",
"type": "channel",
"links": { },
*/

@ -1,287 +0,0 @@
package schema1
import (
"context"
"crypto/sha512"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest"
"github.com/docker/distribution/reference"
"github.com/docker/libtrust"
"github.com/opencontainers/go-digest"
)
type diffID digest.Digest
// gzippedEmptyTar is a gzip-compressed version of an empty tar file
// (1024 NULL bytes)
var gzippedEmptyTar = []byte{
31, 139, 8, 0, 0, 9, 110, 136, 0, 255, 98, 24, 5, 163, 96, 20, 140, 88,
0, 8, 0, 0, 255, 255, 46, 175, 181, 239, 0, 4, 0, 0,
}
// digestSHA256GzippedEmptyTar is the canonical sha256 digest of
// gzippedEmptyTar
const digestSHA256GzippedEmptyTar = digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4")
// configManifestBuilder is a type for constructing manifests from an image
// configuration and generic descriptors.
type configManifestBuilder struct {
// bs is a BlobService used to create empty layer tars in the
// blob store if necessary.
bs distribution.BlobService
// pk is the libtrust private key used to sign the final manifest.
pk libtrust.PrivateKey
// configJSON is configuration supplied when the ManifestBuilder was
// created.
configJSON []byte
// ref contains the name and optional tag provided to NewConfigManifestBuilder.
ref reference.Named
// descriptors is the set of descriptors referencing the layers.
descriptors []distribution.Descriptor
// emptyTarDigest is set to a valid digest if an empty tar has been
// put in the blob store; otherwise it is empty.
emptyTarDigest digest.Digest
}
// NewConfigManifestBuilder is used to build new manifests for the current
// schema version from an image configuration and a set of descriptors.
// It takes a BlobService so that it can add an empty tar to the blob store
// if the resulting manifest needs empty layers.
func NewConfigManifestBuilder(bs distribution.BlobService, pk libtrust.PrivateKey, ref reference.Named, configJSON []byte) distribution.ManifestBuilder {
return &configManifestBuilder{
bs: bs,
pk: pk,
configJSON: configJSON,
ref: ref,
}
}
// Build produces a final manifest from the given references
func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Manifest, err error) {
type imageRootFS struct {
Type string `json:"type"`
DiffIDs []diffID `json:"diff_ids,omitempty"`
BaseLayer string `json:"base_layer,omitempty"`
}
type imageHistory struct {
Created time.Time `json:"created"`
Author string `json:"author,omitempty"`
CreatedBy string `json:"created_by,omitempty"`
Comment string `json:"comment,omitempty"`
EmptyLayer bool `json:"empty_layer,omitempty"`
}
type imageConfig struct {
RootFS *imageRootFS `json:"rootfs,omitempty"`
History []imageHistory `json:"history,omitempty"`
Architecture string `json:"architecture,omitempty"`
}
var img imageConfig
if err := json.Unmarshal(mb.configJSON, &img); err != nil {
return nil, err
}
if len(img.History) == 0 {
return nil, errors.New("empty history when trying to create schema1 manifest")
}
if len(img.RootFS.DiffIDs) != len(mb.descriptors) {
return nil, fmt.Errorf("number of descriptors and number of layers in rootfs must match: len(%v) != len(%v)", img.RootFS.DiffIDs, mb.descriptors)
}
// Generate IDs for each layer
// For non-top-level layers, create fake V1Compatibility strings that
// fit the format and don't collide with anything else, but don't
// result in runnable images on their own.
type v1Compatibility struct {
ID string `json:"id"`
Parent string `json:"parent,omitempty"`
Comment string `json:"comment,omitempty"`
Created time.Time `json:"created"`
ContainerConfig struct {
Cmd []string
} `json:"container_config,omitempty"`
Author string `json:"author,omitempty"`
ThrowAway bool `json:"throwaway,omitempty"`
}
fsLayerList := make([]FSLayer, len(img.History))
history := make([]History, len(img.History))
parent := ""
layerCounter := 0
for i, h := range img.History[:len(img.History)-1] {
var blobsum digest.Digest
if h.EmptyLayer {
if blobsum, err = mb.emptyTar(ctx); err != nil {
return nil, err
}
} else {
if len(img.RootFS.DiffIDs) <= layerCounter {
return nil, errors.New("too many non-empty layers in History section")
}
blobsum = mb.descriptors[layerCounter].Digest
layerCounter++
}
v1ID := digest.FromBytes([]byte(blobsum.Hex() + " " + parent)).Hex()
if i == 0 && img.RootFS.BaseLayer != "" {
// windows-only baselayer setup
baseID := sha512.Sum384([]byte(img.RootFS.BaseLayer))
parent = fmt.Sprintf("%x", baseID[:32])
}
v1Compatibility := v1Compatibility{
ID: v1ID,
Parent: parent,
Comment: h.Comment,
Created: h.Created,
Author: h.Author,
}
v1Compatibility.ContainerConfig.Cmd = []string{img.History[i].CreatedBy}
if h.EmptyLayer {
v1Compatibility.ThrowAway = true
}
jsonBytes, err := json.Marshal(&v1Compatibility)
if err != nil {
return nil, err
}
reversedIndex := len(img.History) - i - 1
history[reversedIndex].V1Compatibility = string(jsonBytes)
fsLayerList[reversedIndex] = FSLayer{BlobSum: blobsum}
parent = v1ID
}
latestHistory := img.History[len(img.History)-1]
var blobsum digest.Digest
if latestHistory.EmptyLayer {
if blobsum, err = mb.emptyTar(ctx); err != nil {
return nil, err
}
} else {
if len(img.RootFS.DiffIDs) <= layerCounter {
return nil, errors.New("too many non-empty layers in History section")
}
blobsum = mb.descriptors[layerCounter].Digest
}
fsLayerList[0] = FSLayer{BlobSum: blobsum}
dgst := digest.FromBytes([]byte(blobsum.Hex() + " " + parent + " " + string(mb.configJSON)))
// Top-level v1compatibility string should be a modified version of the
// image config.
transformedConfig, err := MakeV1ConfigFromConfig(mb.configJSON, dgst.Hex(), parent, latestHistory.EmptyLayer)
if err != nil {
return nil, err
}
history[0].V1Compatibility = string(transformedConfig)
tag := ""
if tagged, isTagged := mb.ref.(reference.Tagged); isTagged {
tag = tagged.Tag()
}
mfst := Manifest{
Versioned: manifest.Versioned{
SchemaVersion: 1,
},
Name: mb.ref.Name(),
Tag: tag,
Architecture: img.Architecture,
FSLayers: fsLayerList,
History: history,
}
return Sign(&mfst, mb.pk)
}
// emptyTar pushes a compressed empty tar to the blob store if one doesn't
// already exist, and returns its blobsum.
func (mb *configManifestBuilder) emptyTar(ctx context.Context) (digest.Digest, error) {
if mb.emptyTarDigest != "" {
// Already put an empty tar
return mb.emptyTarDigest, nil
}
descriptor, err := mb.bs.Stat(ctx, digestSHA256GzippedEmptyTar)
switch err {
case nil:
mb.emptyTarDigest = descriptor.Digest
return descriptor.Digest, nil
case distribution.ErrBlobUnknown:
// nop
default:
return "", err
}
// Add gzipped empty tar to the blob store
descriptor, err = mb.bs.Put(ctx, "", gzippedEmptyTar)
if err != nil {
return "", err
}
mb.emptyTarDigest = descriptor.Digest
return descriptor.Digest, nil
}
// AppendReference adds a reference to the current ManifestBuilder
func (mb *configManifestBuilder) AppendReference(d distribution.Describable) error {
descriptor := d.Descriptor()
if err := descriptor.Digest.Validate(); err != nil {
return err
}
mb.descriptors = append(mb.descriptors, descriptor)
return nil
}
// References returns the current references added to this builder
func (mb *configManifestBuilder) References() []distribution.Descriptor {
return mb.descriptors
}
// MakeV1ConfigFromConfig creates an legacy V1 image config from image config JSON
func MakeV1ConfigFromConfig(configJSON []byte, v1ID, parentV1ID string, throwaway bool) ([]byte, error) {
// Top-level v1compatibility string should be a modified version of the
// image config.
var configAsMap map[string]*json.RawMessage
if err := json.Unmarshal(configJSON, &configAsMap); err != nil {
return nil, err
}
// Delete fields that didn't exist in old manifest
delete(configAsMap, "rootfs")
delete(configAsMap, "history")
configAsMap["id"] = rawJSON(v1ID)
if parentV1ID != "" {
configAsMap["parent"] = rawJSON(parentV1ID)
}
if throwaway {
configAsMap["throwaway"] = rawJSON(true)
}
return json.Marshal(configAsMap)
}
func rawJSON(value interface{}) *json.RawMessage {
jsonval, err := json.Marshal(value)
if err != nil {
return nil
}
return (*json.RawMessage)(&jsonval)
}

@ -1,184 +0,0 @@
package schema1
import (
"encoding/json"
"fmt"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest"
"github.com/docker/libtrust"
"github.com/opencontainers/go-digest"
)
const (
// MediaTypeManifest specifies the mediaType for the current version. Note
// that for schema version 1, the the media is optionally "application/json".
MediaTypeManifest = "application/vnd.docker.distribution.manifest.v1+json"
// MediaTypeSignedManifest specifies the mediatype for current SignedManifest version
MediaTypeSignedManifest = "application/vnd.docker.distribution.manifest.v1+prettyjws"
// MediaTypeManifestLayer specifies the media type for manifest layers
MediaTypeManifestLayer = "application/vnd.docker.container.image.rootfs.diff+x-gtar"
)
var (
// SchemaVersion provides a pre-initialized version structure for this
// packages version of the manifest.
SchemaVersion = manifest.Versioned{
SchemaVersion: 1,
}
)
func init() {
schema1Func := func(b []byte) (distribution.Manifest, distribution.Descriptor, error) {
sm := new(SignedManifest)
err := sm.UnmarshalJSON(b)
if err != nil {
return nil, distribution.Descriptor{}, err
}
desc := distribution.Descriptor{
Digest: digest.FromBytes(sm.Canonical),
Size: int64(len(sm.Canonical)),
MediaType: MediaTypeSignedManifest,
}
return sm, desc, err
}
err := distribution.RegisterManifestSchema(MediaTypeSignedManifest, schema1Func)
if err != nil {
panic(fmt.Sprintf("Unable to register manifest: %s", err))
}
err = distribution.RegisterManifestSchema("", schema1Func)
if err != nil {
panic(fmt.Sprintf("Unable to register manifest: %s", err))
}
err = distribution.RegisterManifestSchema("application/json", schema1Func)
if err != nil {
panic(fmt.Sprintf("Unable to register manifest: %s", err))
}
}
// FSLayer is a container struct for BlobSums defined in an image manifest
type FSLayer struct {
// BlobSum is the tarsum of the referenced filesystem image layer
BlobSum digest.Digest `json:"blobSum"`
}
// History stores unstructured v1 compatibility information
type History struct {
// V1Compatibility is the raw v1 compatibility information
V1Compatibility string `json:"v1Compatibility"`
}
// Manifest provides the base accessible fields for working with V2 image
// format in the registry.
type Manifest struct {
manifest.Versioned
// Name is the name of the image's repository
Name string `json:"name"`
// Tag is the tag of the image specified by this manifest
Tag string `json:"tag"`
// Architecture is the host architecture on which this image is intended to
// run
Architecture string `json:"architecture"`
// FSLayers is a list of filesystem layer blobSums contained in this image
FSLayers []FSLayer `json:"fsLayers"`
// History is a list of unstructured historical data for v1 compatibility
History []History `json:"history"`
}
// SignedManifest provides an envelope for a signed image manifest, including
// the format sensitive raw bytes.
type SignedManifest struct {
Manifest
// Canonical is the canonical byte representation of the ImageManifest,
// without any attached signatures. The manifest byte
// representation cannot change or it will have to be re-signed.
Canonical []byte `json:"-"`
// all contains the byte representation of the Manifest including signatures
// and is returned by Payload()
all []byte
}
// UnmarshalJSON populates a new SignedManifest struct from JSON data.
func (sm *SignedManifest) UnmarshalJSON(b []byte) error {
sm.all = make([]byte, len(b), len(b))
// store manifest and signatures in all
copy(sm.all, b)
jsig, err := libtrust.ParsePrettySignature(b, "signatures")
if err != nil {
return err
}
// Resolve the payload in the manifest.
bytes, err := jsig.Payload()
if err != nil {
return err
}
// sm.Canonical stores the canonical manifest JSON
sm.Canonical = make([]byte, len(bytes), len(bytes))
copy(sm.Canonical, bytes)
// Unmarshal canonical JSON into Manifest object
var manifest Manifest
if err := json.Unmarshal(sm.Canonical, &manifest); err != nil {
return err
}
sm.Manifest = manifest
return nil
}
// References returns the descriptors of this manifests references
func (sm SignedManifest) References() []distribution.Descriptor {
dependencies := make([]distribution.Descriptor, len(sm.FSLayers))
for i, fsLayer := range sm.FSLayers {
dependencies[i] = distribution.Descriptor{
MediaType: "application/vnd.docker.container.image.rootfs.diff+x-gtar",
Digest: fsLayer.BlobSum,
}
}
return dependencies
}
// MarshalJSON returns the contents of raw. If Raw is nil, marshals the inner
// contents. Applications requiring a marshaled signed manifest should simply
// use Raw directly, since the the content produced by json.Marshal will be
// compacted and will fail signature checks.
func (sm *SignedManifest) MarshalJSON() ([]byte, error) {
if len(sm.all) > 0 {
return sm.all, nil
}
// If the raw data is not available, just dump the inner content.
return json.Marshal(&sm.Manifest)
}
// Payload returns the signed content of the signed manifest.
func (sm SignedManifest) Payload() (string, []byte, error) {
return MediaTypeSignedManifest, sm.all, nil
}
// Signatures returns the signatures as provided by
// (*libtrust.JSONSignature).Signatures. The byte slices are opaque jws
// signatures.
func (sm *SignedManifest) Signatures() ([][]byte, error) {
jsig, err := libtrust.ParsePrettySignature(sm.all, "signatures")
if err != nil {
return nil, err
}
// Resolve the payload in the manifest.
return jsig.Signatures()
}

@ -1,98 +0,0 @@
package schema1
import (
"context"
"errors"
"fmt"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest"
"github.com/docker/distribution/reference"
"github.com/docker/libtrust"
"github.com/opencontainers/go-digest"
)
// referenceManifestBuilder is a type for constructing manifests from schema1
// dependencies.
type referenceManifestBuilder struct {
Manifest
pk libtrust.PrivateKey
}
// NewReferenceManifestBuilder is used to build new manifests for the current
// schema version using schema1 dependencies.
func NewReferenceManifestBuilder(pk libtrust.PrivateKey, ref reference.Named, architecture string) distribution.ManifestBuilder {
tag := ""
if tagged, isTagged := ref.(reference.Tagged); isTagged {
tag = tagged.Tag()
}
return &referenceManifestBuilder{
Manifest: Manifest{
Versioned: manifest.Versioned{
SchemaVersion: 1,
},
Name: ref.Name(),
Tag: tag,
Architecture: architecture,
},
pk: pk,
}
}
func (mb *referenceManifestBuilder) Build(ctx context.Context) (distribution.Manifest, error) {
m := mb.Manifest
if len(m.FSLayers) == 0 {
return nil, errors.New("cannot build manifest with zero layers or history")
}
m.FSLayers = make([]FSLayer, len(mb.Manifest.FSLayers))
m.History = make([]History, len(mb.Manifest.History))
copy(m.FSLayers, mb.Manifest.FSLayers)
copy(m.History, mb.Manifest.History)
return Sign(&m, mb.pk)
}
// AppendReference adds a reference to the current ManifestBuilder
func (mb *referenceManifestBuilder) AppendReference(d distribution.Describable) error {
r, ok := d.(Reference)
if !ok {
return fmt.Errorf("Unable to add non-reference type to v1 builder")
}
// Entries need to be prepended
mb.Manifest.FSLayers = append([]FSLayer{{BlobSum: r.Digest}}, mb.Manifest.FSLayers...)
mb.Manifest.History = append([]History{r.History}, mb.Manifest.History...)
return nil
}
// References returns the current references added to this builder
func (mb *referenceManifestBuilder) References() []distribution.Descriptor {
refs := make([]distribution.Descriptor, len(mb.Manifest.FSLayers))
for i := range mb.Manifest.FSLayers {
layerDigest := mb.Manifest.FSLayers[i].BlobSum
history := mb.Manifest.History[i]
ref := Reference{layerDigest, 0, history}
refs[i] = ref.Descriptor()
}
return refs
}
// Reference describes a manifest v2, schema version 1 dependency.
// An FSLayer associated with a history entry.
type Reference struct {
Digest digest.Digest
Size int64 // if we know it, set it for the descriptor.
History History
}
// Descriptor describes a reference
func (r Reference) Descriptor() distribution.Descriptor {
return distribution.Descriptor{
MediaType: MediaTypeManifestLayer,
Digest: r.Digest,
Size: r.Size,
}
}

@ -1,68 +0,0 @@
package schema1
import (
"crypto/x509"
"encoding/json"
"github.com/docker/libtrust"
)
// Sign signs the manifest with the provided private key, returning a
// SignedManifest. This typically won't be used within the registry, except
// for testing.
func Sign(m *Manifest, pk libtrust.PrivateKey) (*SignedManifest, error) {
p, err := json.MarshalIndent(m, "", " ")
if err != nil {
return nil, err
}
js, err := libtrust.NewJSONSignature(p)
if err != nil {
return nil, err
}
if err := js.Sign(pk); err != nil {
return nil, err
}
pretty, err := js.PrettySignature("signatures")
if err != nil {
return nil, err
}
return &SignedManifest{
Manifest: *m,
all: pretty,
Canonical: p,
}, nil
}
// SignWithChain signs the manifest with the given private key and x509 chain.
// The public key of the first element in the chain must be the public key
// corresponding with the sign key.
func SignWithChain(m *Manifest, key libtrust.PrivateKey, chain []*x509.Certificate) (*SignedManifest, error) {
p, err := json.MarshalIndent(m, "", " ")
if err != nil {
return nil, err
}
js, err := libtrust.NewJSONSignature(p)
if err != nil {
return nil, err
}
if err := js.SignWithChain(key, chain); err != nil {
return nil, err
}
pretty, err := js.PrettySignature("signatures")
if err != nil {
return nil, err
}
return &SignedManifest{
Manifest: *m,
all: pretty,
Canonical: p,
}, nil
}

@ -1,32 +0,0 @@
package schema1
import (
"crypto/x509"
"github.com/docker/libtrust"
"github.com/sirupsen/logrus"
)
// Verify verifies the signature of the signed manifest returning the public
// keys used during signing.
func Verify(sm *SignedManifest) ([]libtrust.PublicKey, error) {
js, err := libtrust.ParsePrettySignature(sm.all, "signatures")
if err != nil {
logrus.WithField("err", err).Debugf("(*SignedManifest).Verify")
return nil, err
}
return js.Verify()
}
// VerifyChains verifies the signature of the signed manifest against the
// certificate pool returning the list of verified chains. Signatures without
// an x509 chain are not checked.
func VerifyChains(sm *SignedManifest, ca *x509.CertPool) ([][]*x509.Certificate, error) {
js, err := libtrust.ParsePrettySignature(sm.all, "signatures")
if err != nil {
return nil, err
}
return js.VerifyChains(ca)
}

@ -1,13 +0,0 @@
# Contributing to libtrust
Want to hack on libtrust? Awesome! Here are instructions to get you
started.
libtrust is a part of the [Docker](https://www.docker.com) project, and follows
the same rules and principles. If you're already familiar with the way
Docker does things, you'll feel right at home.
Otherwise, go read
[Docker's contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md).
Happy hacking!

@ -1,191 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2014 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -1,3 +0,0 @@
Solomon Hykes <solomon@docker.com>
Josh Hawn <josh@docker.com> (github: jlhawn)
Derek McGowan <derek@docker.com> (github: dmcgowan)

@ -1,22 +0,0 @@
# libtrust
> **WARNING** this library is no longer actively developed, and will be integrated
> in the [docker/distribution][https://www.github.com/docker/distribution]
> repository in future.
Libtrust is library for managing authentication and authorization using public key cryptography.
Authentication is handled using the identity attached to the public key.
Libtrust provides multiple methods to prove possession of the private key associated with an identity.
- TLS x509 certificates
- Signature verification
- Key Challenge
Authorization and access control is managed through a distributed trust graph.
Trust servers are used as the authorities of the trust graph and allow caching portions of the graph for faster access.
## Copyright and license
Code and documentation copyright 2014 Docker, inc. Code released under the Apache 2.0 license.
Docs released under Creative commons.

@ -1,175 +0,0 @@
package libtrust
import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"io/ioutil"
"math/big"
"net"
"time"
)
type certTemplateInfo struct {
commonName string
domains []string
ipAddresses []net.IP
isCA bool
clientAuth bool
serverAuth bool
}
func generateCertTemplate(info *certTemplateInfo) *x509.Certificate {
// Generate a certificate template which is valid from the past week to
// 10 years from now. The usage of the certificate depends on the
// specified fields in the given certTempInfo object.
var (
keyUsage x509.KeyUsage
extKeyUsage []x509.ExtKeyUsage
)
if info.isCA {
keyUsage = x509.KeyUsageCertSign
}
if info.clientAuth {
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageClientAuth)
}
if info.serverAuth {
extKeyUsage = append(extKeyUsage, x509.ExtKeyUsageServerAuth)
}
return &x509.Certificate{
SerialNumber: big.NewInt(0),
Subject: pkix.Name{
CommonName: info.commonName,
},
NotBefore: time.Now().Add(-time.Hour * 24 * 7),
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10),
DNSNames: info.domains,
IPAddresses: info.ipAddresses,
IsCA: info.isCA,
KeyUsage: keyUsage,
ExtKeyUsage: extKeyUsage,
BasicConstraintsValid: info.isCA,
}
}
func generateCert(pub PublicKey, priv PrivateKey, subInfo, issInfo *certTemplateInfo) (cert *x509.Certificate, err error) {
pubCertTemplate := generateCertTemplate(subInfo)
privCertTemplate := generateCertTemplate(issInfo)
certDER, err := x509.CreateCertificate(
rand.Reader, pubCertTemplate, privCertTemplate,
pub.CryptoPublicKey(), priv.CryptoPrivateKey(),
)
if err != nil {
return nil, fmt.Errorf("failed to create certificate: %s", err)
}
cert, err = x509.ParseCertificate(certDER)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %s", err)
}
return
}
// GenerateSelfSignedServerCert creates a self-signed certificate for the
// given key which is to be used for TLS servers with the given domains and
// IP addresses.
func GenerateSelfSignedServerCert(key PrivateKey, domains []string, ipAddresses []net.IP) (*x509.Certificate, error) {
info := &certTemplateInfo{
commonName: key.KeyID(),
domains: domains,
ipAddresses: ipAddresses,
serverAuth: true,
}
return generateCert(key.PublicKey(), key, info, info)
}
// GenerateSelfSignedClientCert creates a self-signed certificate for the
// given key which is to be used for TLS clients.
func GenerateSelfSignedClientCert(key PrivateKey) (*x509.Certificate, error) {
info := &certTemplateInfo{
commonName: key.KeyID(),
clientAuth: true,
}
return generateCert(key.PublicKey(), key, info, info)
}
// GenerateCACert creates a certificate which can be used as a trusted
// certificate authority.
func GenerateCACert(signer PrivateKey, trustedKey PublicKey) (*x509.Certificate, error) {
subjectInfo := &certTemplateInfo{
commonName: trustedKey.KeyID(),
isCA: true,
}
issuerInfo := &certTemplateInfo{
commonName: signer.KeyID(),
}
return generateCert(trustedKey, signer, subjectInfo, issuerInfo)
}
// GenerateCACertPool creates a certificate authority pool to be used for a
// TLS configuration. Any self-signed certificates issued by the specified
// trusted keys will be verified during a TLS handshake
func GenerateCACertPool(signer PrivateKey, trustedKeys []PublicKey) (*x509.CertPool, error) {
certPool := x509.NewCertPool()
for _, trustedKey := range trustedKeys {
cert, err := GenerateCACert(signer, trustedKey)
if err != nil {
return nil, fmt.Errorf("failed to generate CA certificate: %s", err)
}
certPool.AddCert(cert)
}
return certPool, nil
}
// LoadCertificateBundle loads certificates from the given file. The file should be pem encoded
// containing one or more certificates. The expected pem type is "CERTIFICATE".
func LoadCertificateBundle(filename string) ([]*x509.Certificate, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
certificates := []*x509.Certificate{}
var block *pem.Block
block, b = pem.Decode(b)
for ; block != nil; block, b = pem.Decode(b) {
if block.Type == "CERTIFICATE" {
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
certificates = append(certificates, cert)
} else {
return nil, fmt.Errorf("invalid pem block type: %s", block.Type)
}
}
return certificates, nil
}
// LoadCertificatePool loads a CA pool from the given file. The file should be pem encoded
// containing one or more certificates. The expected pem type is "CERTIFICATE".
func LoadCertificatePool(filename string) (*x509.CertPool, error) {
certs, err := LoadCertificateBundle(filename)
if err != nil {
return nil, err
}
pool := x509.NewCertPool()
for _, cert := range certs {
pool.AddCert(cert)
}
return pool, nil
}

@ -1,9 +0,0 @@
/*
Package libtrust provides an interface for managing authentication and
authorization using public key cryptography. Authentication is handled
using the identity attached to the public key and verified through TLS
x509 certificates, a key challenge, or signature. Authorization and
access control is managed through a trust graph distributed between
both remote trust servers and locally cached and managed data.
*/
package libtrust

@ -1,428 +0,0 @@
package libtrust
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
)
/*
* EC DSA PUBLIC KEY
*/
// ecPublicKey implements a libtrust.PublicKey using elliptic curve digital
// signature algorithms.
type ecPublicKey struct {
*ecdsa.PublicKey
curveName string
signatureAlgorithm *signatureAlgorithm
extended map[string]interface{}
}
func fromECPublicKey(cryptoPublicKey *ecdsa.PublicKey) (*ecPublicKey, error) {
curve := cryptoPublicKey.Curve
switch {
case curve == elliptic.P256():
return &ecPublicKey{cryptoPublicKey, "P-256", es256, map[string]interface{}{}}, nil
case curve == elliptic.P384():
return &ecPublicKey{cryptoPublicKey, "P-384", es384, map[string]interface{}{}}, nil
case curve == elliptic.P521():
return &ecPublicKey{cryptoPublicKey, "P-521", es512, map[string]interface{}{}}, nil
default:
return nil, errors.New("unsupported elliptic curve")
}
}
// KeyType returns the key type for elliptic curve keys, i.e., "EC".
func (k *ecPublicKey) KeyType() string {
return "EC"
}
// CurveName returns the elliptic curve identifier.
// Possible values are "P-256", "P-384", and "P-521".
func (k *ecPublicKey) CurveName() string {
return k.curveName
}
// KeyID returns a distinct identifier which is unique to this Public Key.
func (k *ecPublicKey) KeyID() string {
return keyIDFromCryptoKey(k)
}
func (k *ecPublicKey) String() string {
return fmt.Sprintf("EC Public Key <%s>", k.KeyID())
}
// Verify verifyies the signature of the data in the io.Reader using this
// PublicKey. The alg parameter should identify the digital signature
// algorithm which was used to produce the signature and should be supported
// by this public key. Returns a nil error if the signature is valid.
func (k *ecPublicKey) Verify(data io.Reader, alg string, signature []byte) error {
// For EC keys there is only one supported signature algorithm depending
// on the curve parameters.
if k.signatureAlgorithm.HeaderParam() != alg {
return fmt.Errorf("unable to verify signature: EC Public Key with curve %q does not support signature algorithm %q", k.curveName, alg)
}
// signature is the concatenation of (r, s), base64Url encoded.
sigLength := len(signature)
expectedOctetLength := 2 * ((k.Params().BitSize + 7) >> 3)
if sigLength != expectedOctetLength {
return fmt.Errorf("signature length is %d octets long, should be %d", sigLength, expectedOctetLength)
}
rBytes, sBytes := signature[:sigLength/2], signature[sigLength/2:]
r := new(big.Int).SetBytes(rBytes)
s := new(big.Int).SetBytes(sBytes)
hasher := k.signatureAlgorithm.HashID().New()
_, err := io.Copy(hasher, data)
if err != nil {
return fmt.Errorf("error reading data to sign: %s", err)
}
hash := hasher.Sum(nil)
if !ecdsa.Verify(k.PublicKey, hash, r, s) {
return errors.New("invalid signature")
}
return nil
}
// CryptoPublicKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The type
// is either *rsa.PublicKey or *ecdsa.PublicKey
func (k *ecPublicKey) CryptoPublicKey() crypto.PublicKey {
return k.PublicKey
}
func (k *ecPublicKey) toMap() map[string]interface{} {
jwk := make(map[string]interface{})
for k, v := range k.extended {
jwk[k] = v
}
jwk["kty"] = k.KeyType()
jwk["kid"] = k.KeyID()
jwk["crv"] = k.CurveName()
xBytes := k.X.Bytes()
yBytes := k.Y.Bytes()
octetLength := (k.Params().BitSize + 7) >> 3
// MUST include leading zeros in the output so that x, y are each
// *octetLength* bytes long.
xBuf := make([]byte, octetLength-len(xBytes), octetLength)
yBuf := make([]byte, octetLength-len(yBytes), octetLength)
xBuf = append(xBuf, xBytes...)
yBuf = append(yBuf, yBytes...)
jwk["x"] = joseBase64UrlEncode(xBuf)
jwk["y"] = joseBase64UrlEncode(yBuf)
return jwk
}
// MarshalJSON serializes this Public Key using the JWK JSON serialization format for
// elliptic curve keys.
func (k *ecPublicKey) MarshalJSON() (data []byte, err error) {
return json.Marshal(k.toMap())
}
// PEMBlock serializes this Public Key to DER-encoded PKIX format.
func (k *ecPublicKey) PEMBlock() (*pem.Block, error) {
derBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey)
if err != nil {
return nil, fmt.Errorf("unable to serialize EC PublicKey to DER-encoded PKIX format: %s", err)
}
k.extended["kid"] = k.KeyID() // For display purposes.
return createPemBlock("PUBLIC KEY", derBytes, k.extended)
}
func (k *ecPublicKey) AddExtendedField(field string, value interface{}) {
k.extended[field] = value
}
func (k *ecPublicKey) GetExtendedField(field string) interface{} {
v, ok := k.extended[field]
if !ok {
return nil
}
return v
}
func ecPublicKeyFromMap(jwk map[string]interface{}) (*ecPublicKey, error) {
// JWK key type (kty) has already been determined to be "EC".
// Need to extract 'crv', 'x', 'y', and 'kid' and check for
// consistency.
// Get the curve identifier value.
crv, err := stringFromMap(jwk, "crv")
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key curve identifier: %s", err)
}
var (
curve elliptic.Curve
sigAlg *signatureAlgorithm
)
switch {
case crv == "P-256":
curve = elliptic.P256()
sigAlg = es256
case crv == "P-384":
curve = elliptic.P384()
sigAlg = es384
case crv == "P-521":
curve = elliptic.P521()
sigAlg = es512
default:
return nil, fmt.Errorf("JWK EC Public Key curve identifier not supported: %q\n", crv)
}
// Get the X and Y coordinates for the public key point.
xB64Url, err := stringFromMap(jwk, "x")
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err)
}
x, err := parseECCoordinate(xB64Url, curve)
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key x-coordinate: %s", err)
}
yB64Url, err := stringFromMap(jwk, "y")
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err)
}
y, err := parseECCoordinate(yB64Url, curve)
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key y-coordinate: %s", err)
}
key := &ecPublicKey{
PublicKey: &ecdsa.PublicKey{Curve: curve, X: x, Y: y},
curveName: crv, signatureAlgorithm: sigAlg,
}
// Key ID is optional too, but if it exists, it should match the key.
_, ok := jwk["kid"]
if ok {
kid, err := stringFromMap(jwk, "kid")
if err != nil {
return nil, fmt.Errorf("JWK EC Public Key ID: %s", err)
}
if kid != key.KeyID() {
return nil, fmt.Errorf("JWK EC Public Key ID does not match: %s", kid)
}
}
key.extended = jwk
return key, nil
}
/*
* EC DSA PRIVATE KEY
*/
// ecPrivateKey implements a JWK Private Key using elliptic curve digital signature
// algorithms.
type ecPrivateKey struct {
ecPublicKey
*ecdsa.PrivateKey
}
func fromECPrivateKey(cryptoPrivateKey *ecdsa.PrivateKey) (*ecPrivateKey, error) {
publicKey, err := fromECPublicKey(&cryptoPrivateKey.PublicKey)
if err != nil {
return nil, err
}
return &ecPrivateKey{*publicKey, cryptoPrivateKey}, nil
}
// PublicKey returns the Public Key data associated with this Private Key.
func (k *ecPrivateKey) PublicKey() PublicKey {
return &k.ecPublicKey
}
func (k *ecPrivateKey) String() string {
return fmt.Sprintf("EC Private Key <%s>", k.KeyID())
}
// Sign signs the data read from the io.Reader using a signature algorithm supported
// by the elliptic curve private key. If the specified hashing algorithm is
// supported by this key, that hash function is used to generate the signature
// otherwise the the default hashing algorithm for this key is used. Returns
// the signature and the name of the JWK signature algorithm used, e.g.,
// "ES256", "ES384", "ES512".
func (k *ecPrivateKey) Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) {
// Generate a signature of the data using the internal alg.
// The given hashId is only a suggestion, and since EC keys only support
// on signature/hash algorithm given the curve name, we disregard it for
// the elliptic curve JWK signature implementation.
hasher := k.signatureAlgorithm.HashID().New()
_, err = io.Copy(hasher, data)
if err != nil {
return nil, "", fmt.Errorf("error reading data to sign: %s", err)
}
hash := hasher.Sum(nil)
r, s, err := ecdsa.Sign(rand.Reader, k.PrivateKey, hash)
if err != nil {
return nil, "", fmt.Errorf("error producing signature: %s", err)
}
rBytes, sBytes := r.Bytes(), s.Bytes()
octetLength := (k.ecPublicKey.Params().BitSize + 7) >> 3
// MUST include leading zeros in the output
rBuf := make([]byte, octetLength-len(rBytes), octetLength)
sBuf := make([]byte, octetLength-len(sBytes), octetLength)
rBuf = append(rBuf, rBytes...)
sBuf = append(sBuf, sBytes...)
signature = append(rBuf, sBuf...)
alg = k.signatureAlgorithm.HeaderParam()
return
}
// CryptoPrivateKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The type
// is either *rsa.PublicKey or *ecdsa.PublicKey
func (k *ecPrivateKey) CryptoPrivateKey() crypto.PrivateKey {
return k.PrivateKey
}
func (k *ecPrivateKey) toMap() map[string]interface{} {
jwk := k.ecPublicKey.toMap()
dBytes := k.D.Bytes()
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
// octets (where n is the order of the curve). This is because the private
// key d must be in the interval [1, n-1] so the bitlength of d should be
// no larger than the bitlength of n-1. The easiest way to find the octet
// length is to take bitlength(n-1), add 7 to force a carry, and shift this
// bit sequence right by 3, which is essentially dividing by 8 and adding
// 1 if there is any remainder. Thus, the private key value d should be
// output to (bitlength(n-1)+7)>>3 octets.
n := k.ecPublicKey.Params().N
octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3
// Create a buffer with the necessary zero-padding.
dBuf := make([]byte, octetLength-len(dBytes), octetLength)
dBuf = append(dBuf, dBytes...)
jwk["d"] = joseBase64UrlEncode(dBuf)
return jwk
}
// MarshalJSON serializes this Private Key using the JWK JSON serialization format for
// elliptic curve keys.
func (k *ecPrivateKey) MarshalJSON() (data []byte, err error) {
return json.Marshal(k.toMap())
}
// PEMBlock serializes this Private Key to DER-encoded PKIX format.
func (k *ecPrivateKey) PEMBlock() (*pem.Block, error) {
derBytes, err := x509.MarshalECPrivateKey(k.PrivateKey)
if err != nil {
return nil, fmt.Errorf("unable to serialize EC PrivateKey to DER-encoded PKIX format: %s", err)
}
k.extended["keyID"] = k.KeyID() // For display purposes.
return createPemBlock("EC PRIVATE KEY", derBytes, k.extended)
}
func ecPrivateKeyFromMap(jwk map[string]interface{}) (*ecPrivateKey, error) {
dB64Url, err := stringFromMap(jwk, "d")
if err != nil {
return nil, fmt.Errorf("JWK EC Private Key: %s", err)
}
// JWK key type (kty) has already been determined to be "EC".
// Need to extract the public key information, then extract the private
// key value 'd'.
publicKey, err := ecPublicKeyFromMap(jwk)
if err != nil {
return nil, err
}
d, err := parseECPrivateParam(dB64Url, publicKey.Curve)
if err != nil {
return nil, fmt.Errorf("JWK EC Private Key d-param: %s", err)
}
key := &ecPrivateKey{
ecPublicKey: *publicKey,
PrivateKey: &ecdsa.PrivateKey{
PublicKey: *publicKey.PublicKey,
D: d,
},
}
return key, nil
}
/*
* Key Generation Functions.
*/
func generateECPrivateKey(curve elliptic.Curve) (k *ecPrivateKey, err error) {
k = new(ecPrivateKey)
k.PrivateKey, err = ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, err
}
k.ecPublicKey.PublicKey = &k.PrivateKey.PublicKey
k.extended = make(map[string]interface{})
return
}
// GenerateECP256PrivateKey generates a key pair using elliptic curve P-256.
func GenerateECP256PrivateKey() (PrivateKey, error) {
k, err := generateECPrivateKey(elliptic.P256())
if err != nil {
return nil, fmt.Errorf("error generating EC P-256 key: %s", err)
}
k.curveName = "P-256"
k.signatureAlgorithm = es256
return k, nil
}
// GenerateECP384PrivateKey generates a key pair using elliptic curve P-384.
func GenerateECP384PrivateKey() (PrivateKey, error) {
k, err := generateECPrivateKey(elliptic.P384())
if err != nil {
return nil, fmt.Errorf("error generating EC P-384 key: %s", err)
}
k.curveName = "P-384"
k.signatureAlgorithm = es384
return k, nil
}
// GenerateECP521PrivateKey generates aß key pair using elliptic curve P-521.
func GenerateECP521PrivateKey() (PrivateKey, error) {
k, err := generateECPrivateKey(elliptic.P521())
if err != nil {
return nil, fmt.Errorf("error generating EC P-521 key: %s", err)
}
k.curveName = "P-521"
k.signatureAlgorithm = es512
return k, nil
}

@ -1,50 +0,0 @@
package libtrust
import (
"path/filepath"
)
// FilterByHosts filters the list of PublicKeys to only those which contain a
// 'hosts' pattern which matches the given host. If *includeEmpty* is true,
// then keys which do not specify any hosts are also returned.
func FilterByHosts(keys []PublicKey, host string, includeEmpty bool) ([]PublicKey, error) {
filtered := make([]PublicKey, 0, len(keys))
for _, pubKey := range keys {
var hosts []string
switch v := pubKey.GetExtendedField("hosts").(type) {
case []string:
hosts = v
case []interface{}:
for _, value := range v {
h, ok := value.(string)
if !ok {
continue
}
hosts = append(hosts, h)
}
}
if len(hosts) == 0 {
if includeEmpty {
filtered = append(filtered, pubKey)
}
continue
}
// Check if any hosts match pattern
for _, hostPattern := range hosts {
match, err := filepath.Match(hostPattern, host)
if err != nil {
return nil, err
}
if match {
filtered = append(filtered, pubKey)
continue
}
}
}
return filtered, nil
}

@ -1,56 +0,0 @@
package libtrust
import (
"crypto"
_ "crypto/sha256" // Registrer SHA224 and SHA256
_ "crypto/sha512" // Registrer SHA384 and SHA512
"fmt"
)
type signatureAlgorithm struct {
algHeaderParam string
hashID crypto.Hash
}
func (h *signatureAlgorithm) HeaderParam() string {
return h.algHeaderParam
}
func (h *signatureAlgorithm) HashID() crypto.Hash {
return h.hashID
}
var (
rs256 = &signatureAlgorithm{"RS256", crypto.SHA256}
rs384 = &signatureAlgorithm{"RS384", crypto.SHA384}
rs512 = &signatureAlgorithm{"RS512", crypto.SHA512}
es256 = &signatureAlgorithm{"ES256", crypto.SHA256}
es384 = &signatureAlgorithm{"ES384", crypto.SHA384}
es512 = &signatureAlgorithm{"ES512", crypto.SHA512}
)
func rsaSignatureAlgorithmByName(alg string) (*signatureAlgorithm, error) {
switch {
case alg == "RS256":
return rs256, nil
case alg == "RS384":
return rs384, nil
case alg == "RS512":
return rs512, nil
default:
return nil, fmt.Errorf("RSA Digital Signature Algorithm %q not supported", alg)
}
}
func rsaPKCS1v15SignatureAlgorithmForHashID(hashID crypto.Hash) *signatureAlgorithm {
switch {
case hashID == crypto.SHA512:
return rs512
case hashID == crypto.SHA384:
return rs384
case hashID == crypto.SHA256:
fallthrough
default:
return rs256
}
}

@ -1,657 +0,0 @@
package libtrust
import (
"bytes"
"crypto"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"sort"
"time"
"unicode"
)
var (
// ErrInvalidSignContent is used when the content to be signed is invalid.
ErrInvalidSignContent = errors.New("invalid sign content")
// ErrInvalidJSONContent is used when invalid json is encountered.
ErrInvalidJSONContent = errors.New("invalid json content")
// ErrMissingSignatureKey is used when the specified signature key
// does not exist in the JSON content.
ErrMissingSignatureKey = errors.New("missing signature key")
)
type jsHeader struct {
JWK PublicKey `json:"jwk,omitempty"`
Algorithm string `json:"alg"`
Chain []string `json:"x5c,omitempty"`
}
type jsSignature struct {
Header jsHeader `json:"header"`
Signature string `json:"signature"`
Protected string `json:"protected,omitempty"`
}
type jsSignaturesSorted []jsSignature
func (jsbkid jsSignaturesSorted) Swap(i, j int) { jsbkid[i], jsbkid[j] = jsbkid[j], jsbkid[i] }
func (jsbkid jsSignaturesSorted) Len() int { return len(jsbkid) }
func (jsbkid jsSignaturesSorted) Less(i, j int) bool {
ki, kj := jsbkid[i].Header.JWK.KeyID(), jsbkid[j].Header.JWK.KeyID()
si, sj := jsbkid[i].Signature, jsbkid[j].Signature
if ki == kj {
return si < sj
}
return ki < kj
}
type signKey struct {
PrivateKey
Chain []*x509.Certificate
}
// JSONSignature represents a signature of a json object.
type JSONSignature struct {
payload string
signatures []jsSignature
indent string
formatLength int
formatTail []byte
}
func newJSONSignature() *JSONSignature {
return &JSONSignature{
signatures: make([]jsSignature, 0, 1),
}
}
// Payload returns the encoded payload of the signature. This
// payload should not be signed directly
func (js *JSONSignature) Payload() ([]byte, error) {
return joseBase64UrlDecode(js.payload)
}
func (js *JSONSignature) protectedHeader() (string, error) {
protected := map[string]interface{}{
"formatLength": js.formatLength,
"formatTail": joseBase64UrlEncode(js.formatTail),
"time": time.Now().UTC().Format(time.RFC3339),
}
protectedBytes, err := json.Marshal(protected)
if err != nil {
return "", err
}
return joseBase64UrlEncode(protectedBytes), nil
}
func (js *JSONSignature) signBytes(protectedHeader string) ([]byte, error) {
buf := make([]byte, len(js.payload)+len(protectedHeader)+1)
copy(buf, protectedHeader)
buf[len(protectedHeader)] = '.'
copy(buf[len(protectedHeader)+1:], js.payload)
return buf, nil
}
// Sign adds a signature using the given private key.
func (js *JSONSignature) Sign(key PrivateKey) error {
protected, err := js.protectedHeader()
if err != nil {
return err
}
signBytes, err := js.signBytes(protected)
if err != nil {
return err
}
sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
if err != nil {
return err
}
js.signatures = append(js.signatures, jsSignature{
Header: jsHeader{
JWK: key.PublicKey(),
Algorithm: algorithm,
},
Signature: joseBase64UrlEncode(sigBytes),
Protected: protected,
})
return nil
}
// SignWithChain adds a signature using the given private key
// and setting the x509 chain. The public key of the first element
// in the chain must be the public key corresponding with the sign key.
func (js *JSONSignature) SignWithChain(key PrivateKey, chain []*x509.Certificate) error {
// Ensure key.Chain[0] is public key for key
//key.Chain.PublicKey
//key.PublicKey().CryptoPublicKey()
// Verify chain
protected, err := js.protectedHeader()
if err != nil {
return err
}
signBytes, err := js.signBytes(protected)
if err != nil {
return err
}
sigBytes, algorithm, err := key.Sign(bytes.NewReader(signBytes), crypto.SHA256)
if err != nil {
return err
}
header := jsHeader{
Chain: make([]string, len(chain)),
Algorithm: algorithm,
}
for i, cert := range chain {
header.Chain[i] = base64.StdEncoding.EncodeToString(cert.Raw)
}
js.signatures = append(js.signatures, jsSignature{
Header: header,
Signature: joseBase64UrlEncode(sigBytes),
Protected: protected,
})
return nil
}
// Verify verifies all the signatures and returns the list of
// public keys used to sign. Any x509 chains are not checked.
func (js *JSONSignature) Verify() ([]PublicKey, error) {
keys := make([]PublicKey, len(js.signatures))
for i, signature := range js.signatures {
signBytes, err := js.signBytes(signature.Protected)
if err != nil {
return nil, err
}
var publicKey PublicKey
if len(signature.Header.Chain) > 0 {
certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, err
}
publicKey, err = FromCryptoPublicKey(cert.PublicKey)
if err != nil {
return nil, err
}
} else if signature.Header.JWK != nil {
publicKey = signature.Header.JWK
} else {
return nil, errors.New("missing public key")
}
sigBytes, err := joseBase64UrlDecode(signature.Signature)
if err != nil {
return nil, err
}
err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
if err != nil {
return nil, err
}
keys[i] = publicKey
}
return keys, nil
}
// VerifyChains verifies all the signatures and the chains associated
// with each signature and returns the list of verified chains.
// Signatures without an x509 chain are not checked.
func (js *JSONSignature) VerifyChains(ca *x509.CertPool) ([][]*x509.Certificate, error) {
chains := make([][]*x509.Certificate, 0, len(js.signatures))
for _, signature := range js.signatures {
signBytes, err := js.signBytes(signature.Protected)
if err != nil {
return nil, err
}
var publicKey PublicKey
if len(signature.Header.Chain) > 0 {
certBytes, err := base64.StdEncoding.DecodeString(signature.Header.Chain[0])
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, err
}
publicKey, err = FromCryptoPublicKey(cert.PublicKey)
if err != nil {
return nil, err
}
intermediates := x509.NewCertPool()
if len(signature.Header.Chain) > 1 {
intermediateChain := signature.Header.Chain[1:]
for i := range intermediateChain {
certBytes, err := base64.StdEncoding.DecodeString(intermediateChain[i])
if err != nil {
return nil, err
}
intermediate, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, err
}
intermediates.AddCert(intermediate)
}
}
verifyOptions := x509.VerifyOptions{
Intermediates: intermediates,
Roots: ca,
}
verifiedChains, err := cert.Verify(verifyOptions)
if err != nil {
return nil, err
}
chains = append(chains, verifiedChains...)
sigBytes, err := joseBase64UrlDecode(signature.Signature)
if err != nil {
return nil, err
}
err = publicKey.Verify(bytes.NewReader(signBytes), signature.Header.Algorithm, sigBytes)
if err != nil {
return nil, err
}
}
}
return chains, nil
}
// JWS returns JSON serialized JWS according to
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-7.2
func (js *JSONSignature) JWS() ([]byte, error) {
if len(js.signatures) == 0 {
return nil, errors.New("missing signature")
}
sort.Sort(jsSignaturesSorted(js.signatures))
jsonMap := map[string]interface{}{
"payload": js.payload,
"signatures": js.signatures,
}
return json.MarshalIndent(jsonMap, "", " ")
}
func notSpace(r rune) bool {
return !unicode.IsSpace(r)
}
func detectJSONIndent(jsonContent []byte) (indent string) {
if len(jsonContent) > 2 && jsonContent[0] == '{' && jsonContent[1] == '\n' {
quoteIndex := bytes.IndexRune(jsonContent[1:], '"')
if quoteIndex > 0 {
indent = string(jsonContent[2 : quoteIndex+1])
}
}
return
}
type jsParsedHeader struct {
JWK json.RawMessage `json:"jwk"`
Algorithm string `json:"alg"`
Chain []string `json:"x5c"`
}
type jsParsedSignature struct {
Header jsParsedHeader `json:"header"`
Signature string `json:"signature"`
Protected string `json:"protected"`
}
// ParseJWS parses a JWS serialized JSON object into a Json Signature.
func ParseJWS(content []byte) (*JSONSignature, error) {
type jsParsed struct {
Payload string `json:"payload"`
Signatures []jsParsedSignature `json:"signatures"`
}
parsed := &jsParsed{}
err := json.Unmarshal(content, parsed)
if err != nil {
return nil, err
}
if len(parsed.Signatures) == 0 {
return nil, errors.New("missing signatures")
}
payload, err := joseBase64UrlDecode(parsed.Payload)
if err != nil {
return nil, err
}
js, err := NewJSONSignature(payload)
if err != nil {
return nil, err
}
js.signatures = make([]jsSignature, len(parsed.Signatures))
for i, signature := range parsed.Signatures {
header := jsHeader{
Algorithm: signature.Header.Algorithm,
}
if signature.Header.Chain != nil {
header.Chain = signature.Header.Chain
}
if signature.Header.JWK != nil {
publicKey, err := UnmarshalPublicKeyJWK([]byte(signature.Header.JWK))
if err != nil {
return nil, err
}
header.JWK = publicKey
}
js.signatures[i] = jsSignature{
Header: header,
Signature: signature.Signature,
Protected: signature.Protected,
}
}
return js, nil
}
// NewJSONSignature returns a new unsigned JWS from a json byte array.
// JSONSignature will need to be signed before serializing or storing.
// Optionally, one or more signatures can be provided as byte buffers,
// containing serialized JWS signatures, to assemble a fully signed JWS
// package. It is the callers responsibility to ensure uniqueness of the
// provided signatures.
func NewJSONSignature(content []byte, signatures ...[]byte) (*JSONSignature, error) {
var dataMap map[string]interface{}
err := json.Unmarshal(content, &dataMap)
if err != nil {
return nil, err
}
js := newJSONSignature()
js.indent = detectJSONIndent(content)
js.payload = joseBase64UrlEncode(content)
// Find trailing } and whitespace, put in protected header
closeIndex := bytes.LastIndexFunc(content, notSpace)
if content[closeIndex] != '}' {
return nil, ErrInvalidJSONContent
}
lastRuneIndex := bytes.LastIndexFunc(content[:closeIndex], notSpace)
if content[lastRuneIndex] == ',' {
return nil, ErrInvalidJSONContent
}
js.formatLength = lastRuneIndex + 1
js.formatTail = content[js.formatLength:]
if len(signatures) > 0 {
for _, signature := range signatures {
var parsedJSig jsParsedSignature
if err := json.Unmarshal(signature, &parsedJSig); err != nil {
return nil, err
}
// TODO(stevvooe): A lot of the code below is repeated in
// ParseJWS. It will require more refactoring to fix that.
jsig := jsSignature{
Header: jsHeader{
Algorithm: parsedJSig.Header.Algorithm,
},
Signature: parsedJSig.Signature,
Protected: parsedJSig.Protected,
}
if parsedJSig.Header.Chain != nil {
jsig.Header.Chain = parsedJSig.Header.Chain
}
if parsedJSig.Header.JWK != nil {
publicKey, err := UnmarshalPublicKeyJWK([]byte(parsedJSig.Header.JWK))
if err != nil {
return nil, err
}
jsig.Header.JWK = publicKey
}
js.signatures = append(js.signatures, jsig)
}
}
return js, nil
}
// NewJSONSignatureFromMap returns a new unsigned JSONSignature from a map or
// struct. JWS will need to be signed before serializing or storing.
func NewJSONSignatureFromMap(content interface{}) (*JSONSignature, error) {
switch content.(type) {
case map[string]interface{}:
case struct{}:
default:
return nil, errors.New("invalid data type")
}
js := newJSONSignature()
js.indent = " "
payload, err := json.MarshalIndent(content, "", js.indent)
if err != nil {
return nil, err
}
js.payload = joseBase64UrlEncode(payload)
// Remove '\n}' from formatted section, put in protected header
js.formatLength = len(payload) - 2
js.formatTail = payload[js.formatLength:]
return js, nil
}
func readIntFromMap(key string, m map[string]interface{}) (int, bool) {
value, ok := m[key]
if !ok {
return 0, false
}
switch v := value.(type) {
case int:
return v, true
case float64:
return int(v), true
default:
return 0, false
}
}
func readStringFromMap(key string, m map[string]interface{}) (v string, ok bool) {
value, ok := m[key]
if !ok {
return "", false
}
v, ok = value.(string)
return
}
// ParsePrettySignature parses a formatted signature into a
// JSON signature. If the signatures are missing the format information
// an error is thrown. The formatted signature must be created by
// the same method as format signature.
func ParsePrettySignature(content []byte, signatureKey string) (*JSONSignature, error) {
var contentMap map[string]json.RawMessage
err := json.Unmarshal(content, &contentMap)
if err != nil {
return nil, fmt.Errorf("error unmarshalling content: %s", err)
}
sigMessage, ok := contentMap[signatureKey]
if !ok {
return nil, ErrMissingSignatureKey
}
var signatureBlocks []jsParsedSignature
err = json.Unmarshal([]byte(sigMessage), &signatureBlocks)
if err != nil {
return nil, fmt.Errorf("error unmarshalling signatures: %s", err)
}
js := newJSONSignature()
js.signatures = make([]jsSignature, len(signatureBlocks))
for i, signatureBlock := range signatureBlocks {
protectedBytes, err := joseBase64UrlDecode(signatureBlock.Protected)
if err != nil {
return nil, fmt.Errorf("base64 decode error: %s", err)
}
var protectedHeader map[string]interface{}
err = json.Unmarshal(protectedBytes, &protectedHeader)
if err != nil {
return nil, fmt.Errorf("error unmarshalling protected header: %s", err)
}
formatLength, ok := readIntFromMap("formatLength", protectedHeader)
if !ok {
return nil, errors.New("missing formatted length")
}
encodedTail, ok := readStringFromMap("formatTail", protectedHeader)
if !ok {
return nil, errors.New("missing formatted tail")
}
formatTail, err := joseBase64UrlDecode(encodedTail)
if err != nil {
return nil, fmt.Errorf("base64 decode error on tail: %s", err)
}
if js.formatLength == 0 {
js.formatLength = formatLength
} else if js.formatLength != formatLength {
return nil, errors.New("conflicting format length")
}
if len(js.formatTail) == 0 {
js.formatTail = formatTail
} else if bytes.Compare(js.formatTail, formatTail) != 0 {
return nil, errors.New("conflicting format tail")
}
header := jsHeader{
Algorithm: signatureBlock.Header.Algorithm,
Chain: signatureBlock.Header.Chain,
}
if signatureBlock.Header.JWK != nil {
publicKey, err := UnmarshalPublicKeyJWK([]byte(signatureBlock.Header.JWK))
if err != nil {
return nil, fmt.Errorf("error unmarshalling public key: %s", err)
}
header.JWK = publicKey
}
js.signatures[i] = jsSignature{
Header: header,
Signature: signatureBlock.Signature,
Protected: signatureBlock.Protected,
}
}
if js.formatLength > len(content) {
return nil, errors.New("invalid format length")
}
formatted := make([]byte, js.formatLength+len(js.formatTail))
copy(formatted, content[:js.formatLength])
copy(formatted[js.formatLength:], js.formatTail)
js.indent = detectJSONIndent(formatted)
js.payload = joseBase64UrlEncode(formatted)
return js, nil
}
// PrettySignature formats a json signature into an easy to read
// single json serialized object.
func (js *JSONSignature) PrettySignature(signatureKey string) ([]byte, error) {
if len(js.signatures) == 0 {
return nil, errors.New("no signatures")
}
payload, err := joseBase64UrlDecode(js.payload)
if err != nil {
return nil, err
}
payload = payload[:js.formatLength]
sort.Sort(jsSignaturesSorted(js.signatures))
var marshalled []byte
var marshallErr error
if js.indent != "" {
marshalled, marshallErr = json.MarshalIndent(js.signatures, js.indent, js.indent)
} else {
marshalled, marshallErr = json.Marshal(js.signatures)
}
if marshallErr != nil {
return nil, marshallErr
}
buf := bytes.NewBuffer(make([]byte, 0, len(payload)+len(marshalled)+34))
buf.Write(payload)
buf.WriteByte(',')
if js.indent != "" {
buf.WriteByte('\n')
buf.WriteString(js.indent)
buf.WriteByte('"')
buf.WriteString(signatureKey)
buf.WriteString("\": ")
buf.Write(marshalled)
buf.WriteByte('\n')
} else {
buf.WriteByte('"')
buf.WriteString(signatureKey)
buf.WriteString("\":")
buf.Write(marshalled)
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
// Signatures provides the signatures on this JWS as opaque blobs, sorted by
// keyID. These blobs can be stored and reassembled with payloads. Internally,
// they are simply marshaled json web signatures but implementations should
// not rely on this.
func (js *JSONSignature) Signatures() ([][]byte, error) {
sort.Sort(jsSignaturesSorted(js.signatures))
var sb [][]byte
for _, jsig := range js.signatures {
p, err := json.Marshal(jsig)
if err != nil {
return nil, err
}
sb = append(sb, p)
}
return sb, nil
}
// Merge combines the signatures from one or more other signatures into the
// method receiver. If the payloads differ for any argument, an error will be
// returned and the receiver will not be modified.
func (js *JSONSignature) Merge(others ...*JSONSignature) error {
merged := js.signatures
for _, other := range others {
if js.payload != other.payload {
return fmt.Errorf("payloads differ from merge target")
}
merged = append(merged, other.signatures...)
}
js.signatures = merged
return nil
}

@ -1,253 +0,0 @@
package libtrust
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
)
// PublicKey is a generic interface for a Public Key.
type PublicKey interface {
// KeyType returns the key type for this key. For elliptic curve keys,
// this value should be "EC". For RSA keys, this value should be "RSA".
KeyType() string
// KeyID returns a distinct identifier which is unique to this Public Key.
// The format generated by this library is a base32 encoding of a 240 bit
// hash of the public key data divided into 12 groups like so:
// ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
KeyID() string
// Verify verifyies the signature of the data in the io.Reader using this
// Public Key. The alg parameter should identify the digital signature
// algorithm which was used to produce the signature and should be
// supported by this public key. Returns a nil error if the signature
// is valid.
Verify(data io.Reader, alg string, signature []byte) error
// CryptoPublicKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The type
// is either *rsa.PublicKey or *ecdsa.PublicKey
CryptoPublicKey() crypto.PublicKey
// These public keys can be serialized to the standard JSON encoding for
// JSON Web Keys. See section 6 of the IETF draft RFC for JOSE JSON Web
// Algorithms.
MarshalJSON() ([]byte, error)
// These keys can also be serialized to the standard PEM encoding.
PEMBlock() (*pem.Block, error)
// The string representation of a key is its key type and ID.
String() string
AddExtendedField(string, interface{})
GetExtendedField(string) interface{}
}
// PrivateKey is a generic interface for a Private Key.
type PrivateKey interface {
// A PrivateKey contains all fields and methods of a PublicKey of the
// same type. The MarshalJSON method also outputs the private key as a
// JSON Web Key, and the PEMBlock method outputs the private key as a
// PEM block.
PublicKey
// PublicKey returns the PublicKey associated with this PrivateKey.
PublicKey() PublicKey
// Sign signs the data read from the io.Reader using a signature algorithm
// supported by the private key. If the specified hashing algorithm is
// supported by this key, that hash function is used to generate the
// signature otherwise the the default hashing algorithm for this key is
// used. Returns the signature and identifier of the algorithm used.
Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error)
// CryptoPrivateKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The
// type is either *rsa.PublicKey or *ecdsa.PublicKey
CryptoPrivateKey() crypto.PrivateKey
}
// FromCryptoPublicKey returns a libtrust PublicKey representation of the given
// *ecdsa.PublicKey or *rsa.PublicKey. Returns a non-nil error when the given
// key is of an unsupported type.
func FromCryptoPublicKey(cryptoPublicKey crypto.PublicKey) (PublicKey, error) {
switch cryptoPublicKey := cryptoPublicKey.(type) {
case *ecdsa.PublicKey:
return fromECPublicKey(cryptoPublicKey)
case *rsa.PublicKey:
return fromRSAPublicKey(cryptoPublicKey), nil
default:
return nil, fmt.Errorf("public key type %T is not supported", cryptoPublicKey)
}
}
// FromCryptoPrivateKey returns a libtrust PrivateKey representation of the given
// *ecdsa.PrivateKey or *rsa.PrivateKey. Returns a non-nil error when the given
// key is of an unsupported type.
func FromCryptoPrivateKey(cryptoPrivateKey crypto.PrivateKey) (PrivateKey, error) {
switch cryptoPrivateKey := cryptoPrivateKey.(type) {
case *ecdsa.PrivateKey:
return fromECPrivateKey(cryptoPrivateKey)
case *rsa.PrivateKey:
return fromRSAPrivateKey(cryptoPrivateKey), nil
default:
return nil, fmt.Errorf("private key type %T is not supported", cryptoPrivateKey)
}
}
// UnmarshalPublicKeyPEM parses the PEM encoded data and returns a libtrust
// PublicKey or an error if there is a problem with the encoding.
func UnmarshalPublicKeyPEM(data []byte) (PublicKey, error) {
pemBlock, _ := pem.Decode(data)
if pemBlock == nil {
return nil, errors.New("unable to find PEM encoded data")
} else if pemBlock.Type != "PUBLIC KEY" {
return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type)
}
return pubKeyFromPEMBlock(pemBlock)
}
// UnmarshalPublicKeyPEMBundle parses the PEM encoded data as a bundle of
// PEM blocks appended one after the other and returns a slice of PublicKey
// objects that it finds.
func UnmarshalPublicKeyPEMBundle(data []byte) ([]PublicKey, error) {
pubKeys := []PublicKey{}
for {
var pemBlock *pem.Block
pemBlock, data = pem.Decode(data)
if pemBlock == nil {
break
} else if pemBlock.Type != "PUBLIC KEY" {
return nil, fmt.Errorf("unable to get PublicKey from PEM type: %s", pemBlock.Type)
}
pubKey, err := pubKeyFromPEMBlock(pemBlock)
if err != nil {
return nil, err
}
pubKeys = append(pubKeys, pubKey)
}
return pubKeys, nil
}
// UnmarshalPrivateKeyPEM parses the PEM encoded data and returns a libtrust
// PrivateKey or an error if there is a problem with the encoding.
func UnmarshalPrivateKeyPEM(data []byte) (PrivateKey, error) {
pemBlock, _ := pem.Decode(data)
if pemBlock == nil {
return nil, errors.New("unable to find PEM encoded data")
}
var key PrivateKey
switch {
case pemBlock.Type == "RSA PRIVATE KEY":
rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("unable to decode RSA Private Key PEM data: %s", err)
}
key = fromRSAPrivateKey(rsaPrivateKey)
case pemBlock.Type == "EC PRIVATE KEY":
ecPrivateKey, err := x509.ParseECPrivateKey(pemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("unable to decode EC Private Key PEM data: %s", err)
}
key, err = fromECPrivateKey(ecPrivateKey)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unable to get PrivateKey from PEM type: %s", pemBlock.Type)
}
addPEMHeadersToKey(pemBlock, key.PublicKey())
return key, nil
}
// UnmarshalPublicKeyJWK unmarshals the given JSON Web Key into a generic
// Public Key to be used with libtrust.
func UnmarshalPublicKeyJWK(data []byte) (PublicKey, error) {
jwk := make(map[string]interface{})
err := json.Unmarshal(data, &jwk)
if err != nil {
return nil, fmt.Errorf(
"decoding JWK Public Key JSON data: %s\n", err,
)
}
// Get the Key Type value.
kty, err := stringFromMap(jwk, "kty")
if err != nil {
return nil, fmt.Errorf("JWK Public Key type: %s", err)
}
switch {
case kty == "EC":
// Call out to unmarshal EC public key.
return ecPublicKeyFromMap(jwk)
case kty == "RSA":
// Call out to unmarshal RSA public key.
return rsaPublicKeyFromMap(jwk)
default:
return nil, fmt.Errorf(
"JWK Public Key type not supported: %q\n", kty,
)
}
}
// UnmarshalPublicKeyJWKSet parses the JSON encoded data as a JSON Web Key Set
// and returns a slice of Public Key objects.
func UnmarshalPublicKeyJWKSet(data []byte) ([]PublicKey, error) {
rawKeys, err := loadJSONKeySetRaw(data)
if err != nil {
return nil, err
}
pubKeys := make([]PublicKey, 0, len(rawKeys))
for _, rawKey := range rawKeys {
pubKey, err := UnmarshalPublicKeyJWK(rawKey)
if err != nil {
return nil, err
}
pubKeys = append(pubKeys, pubKey)
}
return pubKeys, nil
}
// UnmarshalPrivateKeyJWK unmarshals the given JSON Web Key into a generic
// Private Key to be used with libtrust.
func UnmarshalPrivateKeyJWK(data []byte) (PrivateKey, error) {
jwk := make(map[string]interface{})
err := json.Unmarshal(data, &jwk)
if err != nil {
return nil, fmt.Errorf(
"decoding JWK Private Key JSON data: %s\n", err,
)
}
// Get the Key Type value.
kty, err := stringFromMap(jwk, "kty")
if err != nil {
return nil, fmt.Errorf("JWK Private Key type: %s", err)
}
switch {
case kty == "EC":
// Call out to unmarshal EC private key.
return ecPrivateKeyFromMap(jwk)
case kty == "RSA":
// Call out to unmarshal RSA private key.
return rsaPrivateKeyFromMap(jwk)
default:
return nil, fmt.Errorf(
"JWK Private Key type not supported: %q\n", kty,
)
}
}

@ -1,255 +0,0 @@
package libtrust
import (
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
)
var (
// ErrKeyFileDoesNotExist indicates that the private key file does not exist.
ErrKeyFileDoesNotExist = errors.New("key file does not exist")
)
func readKeyFileBytes(filename string) ([]byte, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
if os.IsNotExist(err) {
err = ErrKeyFileDoesNotExist
} else {
err = fmt.Errorf("unable to read key file %s: %s", filename, err)
}
return nil, err
}
return data, nil
}
/*
Loading and Saving of Public and Private Keys in either PEM or JWK format.
*/
// LoadKeyFile opens the given filename and attempts to read a Private Key
// encoded in either PEM or JWK format (if .json or .jwk file extension).
func LoadKeyFile(filename string) (PrivateKey, error) {
contents, err := readKeyFileBytes(filename)
if err != nil {
return nil, err
}
var key PrivateKey
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
key, err = UnmarshalPrivateKeyJWK(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode private key JWK: %s", err)
}
} else {
key, err = UnmarshalPrivateKeyPEM(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode private key PEM: %s", err)
}
}
return key, nil
}
// LoadPublicKeyFile opens the given filename and attempts to read a Public Key
// encoded in either PEM or JWK format (if .json or .jwk file extension).
func LoadPublicKeyFile(filename string) (PublicKey, error) {
contents, err := readKeyFileBytes(filename)
if err != nil {
return nil, err
}
var key PublicKey
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
key, err = UnmarshalPublicKeyJWK(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode public key JWK: %s", err)
}
} else {
key, err = UnmarshalPublicKeyPEM(contents)
if err != nil {
return nil, fmt.Errorf("unable to decode public key PEM: %s", err)
}
}
return key, nil
}
// SaveKey saves the given key to a file using the provided filename.
// This process will overwrite any existing file at the provided location.
func SaveKey(filename string, key PrivateKey) error {
var encodedKey []byte
var err error
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
// Encode in JSON Web Key format.
encodedKey, err = json.MarshalIndent(key, "", " ")
if err != nil {
return fmt.Errorf("unable to encode private key JWK: %s", err)
}
} else {
// Encode in PEM format.
pemBlock, err := key.PEMBlock()
if err != nil {
return fmt.Errorf("unable to encode private key PEM: %s", err)
}
encodedKey = pem.EncodeToMemory(pemBlock)
}
err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0600))
if err != nil {
return fmt.Errorf("unable to write private key file %s: %s", filename, err)
}
return nil
}
// SavePublicKey saves the given public key to the file.
func SavePublicKey(filename string, key PublicKey) error {
var encodedKey []byte
var err error
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
// Encode in JSON Web Key format.
encodedKey, err = json.MarshalIndent(key, "", " ")
if err != nil {
return fmt.Errorf("unable to encode public key JWK: %s", err)
}
} else {
// Encode in PEM format.
pemBlock, err := key.PEMBlock()
if err != nil {
return fmt.Errorf("unable to encode public key PEM: %s", err)
}
encodedKey = pem.EncodeToMemory(pemBlock)
}
err = ioutil.WriteFile(filename, encodedKey, os.FileMode(0644))
if err != nil {
return fmt.Errorf("unable to write public key file %s: %s", filename, err)
}
return nil
}
// Public Key Set files
type jwkSet struct {
Keys []json.RawMessage `json:"keys"`
}
// LoadKeySetFile loads a key set
func LoadKeySetFile(filename string) ([]PublicKey, error) {
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
return loadJSONKeySetFile(filename)
}
// Must be a PEM format file
return loadPEMKeySetFile(filename)
}
func loadJSONKeySetRaw(data []byte) ([]json.RawMessage, error) {
if len(data) == 0 {
// This is okay, just return an empty slice.
return []json.RawMessage{}, nil
}
keySet := jwkSet{}
err := json.Unmarshal(data, &keySet)
if err != nil {
return nil, fmt.Errorf("unable to decode JSON Web Key Set: %s", err)
}
return keySet.Keys, nil
}
func loadJSONKeySetFile(filename string) ([]PublicKey, error) {
contents, err := readKeyFileBytes(filename)
if err != nil && err != ErrKeyFileDoesNotExist {
return nil, err
}
return UnmarshalPublicKeyJWKSet(contents)
}
func loadPEMKeySetFile(filename string) ([]PublicKey, error) {
data, err := readKeyFileBytes(filename)
if err != nil && err != ErrKeyFileDoesNotExist {
return nil, err
}
return UnmarshalPublicKeyPEMBundle(data)
}
// AddKeySetFile adds a key to a key set
func AddKeySetFile(filename string, key PublicKey) error {
if strings.HasSuffix(filename, ".json") || strings.HasSuffix(filename, ".jwk") {
return addKeySetJSONFile(filename, key)
}
// Must be a PEM format file
return addKeySetPEMFile(filename, key)
}
func addKeySetJSONFile(filename string, key PublicKey) error {
encodedKey, err := json.Marshal(key)
if err != nil {
return fmt.Errorf("unable to encode trusted client key: %s", err)
}
contents, err := readKeyFileBytes(filename)
if err != nil && err != ErrKeyFileDoesNotExist {
return err
}
rawEntries, err := loadJSONKeySetRaw(contents)
if err != nil {
return err
}
rawEntries = append(rawEntries, json.RawMessage(encodedKey))
entriesWrapper := jwkSet{Keys: rawEntries}
encodedEntries, err := json.MarshalIndent(entriesWrapper, "", " ")
if err != nil {
return fmt.Errorf("unable to encode trusted client keys: %s", err)
}
err = ioutil.WriteFile(filename, encodedEntries, os.FileMode(0644))
if err != nil {
return fmt.Errorf("unable to write trusted client keys file %s: %s", filename, err)
}
return nil
}
func addKeySetPEMFile(filename string, key PublicKey) error {
// Encode to PEM, open file for appending, write PEM.
file, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.FileMode(0644))
if err != nil {
return fmt.Errorf("unable to open trusted client keys file %s: %s", filename, err)
}
defer file.Close()
pemBlock, err := key.PEMBlock()
if err != nil {
return fmt.Errorf("unable to encoded trusted key: %s", err)
}
_, err = file.Write(pem.EncodeToMemory(pemBlock))
if err != nil {
return fmt.Errorf("unable to write trusted keys file: %s", err)
}
return nil
}

@ -1,175 +0,0 @@
package libtrust
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"os"
"path"
"sync"
)
// ClientKeyManager manages client keys on the filesystem
type ClientKeyManager struct {
key PrivateKey
clientFile string
clientDir string
clientLock sync.RWMutex
clients []PublicKey
configLock sync.Mutex
configs []*tls.Config
}
// NewClientKeyManager loads a new manager from a set of key files
// and managed by the given private key.
func NewClientKeyManager(trustKey PrivateKey, clientFile, clientDir string) (*ClientKeyManager, error) {
m := &ClientKeyManager{
key: trustKey,
clientFile: clientFile,
clientDir: clientDir,
}
if err := m.loadKeys(); err != nil {
return nil, err
}
// TODO Start watching file and directory
return m, nil
}
func (c *ClientKeyManager) loadKeys() (err error) {
// Load authorized keys file
var clients []PublicKey
if c.clientFile != "" {
clients, err = LoadKeySetFile(c.clientFile)
if err != nil {
return fmt.Errorf("unable to load authorized keys: %s", err)
}
}
// Add clients from authorized keys directory
files, err := ioutil.ReadDir(c.clientDir)
if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("unable to open authorized keys directory: %s", err)
}
for _, f := range files {
if !f.IsDir() {
publicKey, err := LoadPublicKeyFile(path.Join(c.clientDir, f.Name()))
if err != nil {
return fmt.Errorf("unable to load authorized key file: %s", err)
}
clients = append(clients, publicKey)
}
}
c.clientLock.Lock()
c.clients = clients
c.clientLock.Unlock()
return nil
}
// RegisterTLSConfig registers a tls configuration to manager
// such that any changes to the keys may be reflected in
// the tls client CA pool
func (c *ClientKeyManager) RegisterTLSConfig(tlsConfig *tls.Config) error {
c.clientLock.RLock()
certPool, err := GenerateCACertPool(c.key, c.clients)
if err != nil {
return fmt.Errorf("CA pool generation error: %s", err)
}
c.clientLock.RUnlock()
tlsConfig.ClientCAs = certPool
c.configLock.Lock()
c.configs = append(c.configs, tlsConfig)
c.configLock.Unlock()
return nil
}
// NewIdentityAuthTLSConfig creates a tls.Config for the server to use for
// libtrust identity authentication for the domain specified
func NewIdentityAuthTLSConfig(trustKey PrivateKey, clients *ClientKeyManager, addr string, domain string) (*tls.Config, error) {
tlsConfig := newTLSConfig()
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
if err := clients.RegisterTLSConfig(tlsConfig); err != nil {
return nil, err
}
// Generate cert
ips, domains, err := parseAddr(addr)
if err != nil {
return nil, err
}
// add domain that it expects clients to use
domains = append(domains, domain)
x509Cert, err := GenerateSelfSignedServerCert(trustKey, domains, ips)
if err != nil {
return nil, fmt.Errorf("certificate generation error: %s", err)
}
tlsConfig.Certificates = []tls.Certificate{{
Certificate: [][]byte{x509Cert.Raw},
PrivateKey: trustKey.CryptoPrivateKey(),
Leaf: x509Cert,
}}
return tlsConfig, nil
}
// NewCertAuthTLSConfig creates a tls.Config for the server to use for
// certificate authentication
func NewCertAuthTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) {
tlsConfig := newTLSConfig()
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, fmt.Errorf("Couldn't load X509 key pair (%s, %s): %s. Key encrypted?", certPath, keyPath, err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
// Verify client certificates against a CA?
if caPath != "" {
certPool := x509.NewCertPool()
file, err := ioutil.ReadFile(caPath)
if err != nil {
return nil, fmt.Errorf("Couldn't read CA certificate: %s", err)
}
certPool.AppendCertsFromPEM(file)
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
tlsConfig.ClientCAs = certPool
}
return tlsConfig, nil
}
func newTLSConfig() *tls.Config {
return &tls.Config{
NextProtos: []string{"http/1.1"},
// Avoid fallback on insecure SSL protocols
MinVersion: tls.VersionTLS10,
}
}
// parseAddr parses an address into an array of IPs and domains
func parseAddr(addr string) ([]net.IP, []string, error) {
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, nil, err
}
var domains []string
var ips []net.IP
ip := net.ParseIP(host)
if ip != nil {
ips = []net.IP{ip}
} else {
domains = []string{host}
}
return ips, domains, nil
}

@ -1,427 +0,0 @@
package libtrust
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
)
/*
* RSA DSA PUBLIC KEY
*/
// rsaPublicKey implements a JWK Public Key using RSA digital signature algorithms.
type rsaPublicKey struct {
*rsa.PublicKey
extended map[string]interface{}
}
func fromRSAPublicKey(cryptoPublicKey *rsa.PublicKey) *rsaPublicKey {
return &rsaPublicKey{cryptoPublicKey, map[string]interface{}{}}
}
// KeyType returns the JWK key type for RSA keys, i.e., "RSA".
func (k *rsaPublicKey) KeyType() string {
return "RSA"
}
// KeyID returns a distinct identifier which is unique to this Public Key.
func (k *rsaPublicKey) KeyID() string {
return keyIDFromCryptoKey(k)
}
func (k *rsaPublicKey) String() string {
return fmt.Sprintf("RSA Public Key <%s>", k.KeyID())
}
// Verify verifyies the signature of the data in the io.Reader using this Public Key.
// The alg parameter should be the name of the JWA digital signature algorithm
// which was used to produce the signature and should be supported by this
// public key. Returns a nil error if the signature is valid.
func (k *rsaPublicKey) Verify(data io.Reader, alg string, signature []byte) error {
// Verify the signature of the given date, return non-nil error if valid.
sigAlg, err := rsaSignatureAlgorithmByName(alg)
if err != nil {
return fmt.Errorf("unable to verify Signature: %s", err)
}
hasher := sigAlg.HashID().New()
_, err = io.Copy(hasher, data)
if err != nil {
return fmt.Errorf("error reading data to sign: %s", err)
}
hash := hasher.Sum(nil)
err = rsa.VerifyPKCS1v15(k.PublicKey, sigAlg.HashID(), hash, signature)
if err != nil {
return fmt.Errorf("invalid %s signature: %s", sigAlg.HeaderParam(), err)
}
return nil
}
// CryptoPublicKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The type
// is either *rsa.PublicKey or *ecdsa.PublicKey
func (k *rsaPublicKey) CryptoPublicKey() crypto.PublicKey {
return k.PublicKey
}
func (k *rsaPublicKey) toMap() map[string]interface{} {
jwk := make(map[string]interface{})
for k, v := range k.extended {
jwk[k] = v
}
jwk["kty"] = k.KeyType()
jwk["kid"] = k.KeyID()
jwk["n"] = joseBase64UrlEncode(k.N.Bytes())
jwk["e"] = joseBase64UrlEncode(serializeRSAPublicExponentParam(k.E))
return jwk
}
// MarshalJSON serializes this Public Key using the JWK JSON serialization format for
// RSA keys.
func (k *rsaPublicKey) MarshalJSON() (data []byte, err error) {
return json.Marshal(k.toMap())
}
// PEMBlock serializes this Public Key to DER-encoded PKIX format.
func (k *rsaPublicKey) PEMBlock() (*pem.Block, error) {
derBytes, err := x509.MarshalPKIXPublicKey(k.PublicKey)
if err != nil {
return nil, fmt.Errorf("unable to serialize RSA PublicKey to DER-encoded PKIX format: %s", err)
}
k.extended["kid"] = k.KeyID() // For display purposes.
return createPemBlock("PUBLIC KEY", derBytes, k.extended)
}
func (k *rsaPublicKey) AddExtendedField(field string, value interface{}) {
k.extended[field] = value
}
func (k *rsaPublicKey) GetExtendedField(field string) interface{} {
v, ok := k.extended[field]
if !ok {
return nil
}
return v
}
func rsaPublicKeyFromMap(jwk map[string]interface{}) (*rsaPublicKey, error) {
// JWK key type (kty) has already been determined to be "RSA".
// Need to extract 'n', 'e', and 'kid' and check for
// consistency.
// Get the modulus parameter N.
nB64Url, err := stringFromMap(jwk, "n")
if err != nil {
return nil, fmt.Errorf("JWK RSA Public Key modulus: %s", err)
}
n, err := parseRSAModulusParam(nB64Url)
if err != nil {
return nil, fmt.Errorf("JWK RSA Public Key modulus: %s", err)
}
// Get the public exponent E.
eB64Url, err := stringFromMap(jwk, "e")
if err != nil {
return nil, fmt.Errorf("JWK RSA Public Key exponent: %s", err)
}
e, err := parseRSAPublicExponentParam(eB64Url)
if err != nil {
return nil, fmt.Errorf("JWK RSA Public Key exponent: %s", err)
}
key := &rsaPublicKey{
PublicKey: &rsa.PublicKey{N: n, E: e},
}
// Key ID is optional, but if it exists, it should match the key.
_, ok := jwk["kid"]
if ok {
kid, err := stringFromMap(jwk, "kid")
if err != nil {
return nil, fmt.Errorf("JWK RSA Public Key ID: %s", err)
}
if kid != key.KeyID() {
return nil, fmt.Errorf("JWK RSA Public Key ID does not match: %s", kid)
}
}
if _, ok := jwk["d"]; ok {
return nil, fmt.Errorf("JWK RSA Public Key cannot contain private exponent")
}
key.extended = jwk
return key, nil
}
/*
* RSA DSA PRIVATE KEY
*/
// rsaPrivateKey implements a JWK Private Key using RSA digital signature algorithms.
type rsaPrivateKey struct {
rsaPublicKey
*rsa.PrivateKey
}
func fromRSAPrivateKey(cryptoPrivateKey *rsa.PrivateKey) *rsaPrivateKey {
return &rsaPrivateKey{
*fromRSAPublicKey(&cryptoPrivateKey.PublicKey),
cryptoPrivateKey,
}
}
// PublicKey returns the Public Key data associated with this Private Key.
func (k *rsaPrivateKey) PublicKey() PublicKey {
return &k.rsaPublicKey
}
func (k *rsaPrivateKey) String() string {
return fmt.Sprintf("RSA Private Key <%s>", k.KeyID())
}
// Sign signs the data read from the io.Reader using a signature algorithm supported
// by the RSA private key. If the specified hashing algorithm is supported by
// this key, that hash function is used to generate the signature otherwise the
// the default hashing algorithm for this key is used. Returns the signature
// and the name of the JWK signature algorithm used, e.g., "RS256", "RS384",
// "RS512".
func (k *rsaPrivateKey) Sign(data io.Reader, hashID crypto.Hash) (signature []byte, alg string, err error) {
// Generate a signature of the data using the internal alg.
sigAlg := rsaPKCS1v15SignatureAlgorithmForHashID(hashID)
hasher := sigAlg.HashID().New()
_, err = io.Copy(hasher, data)
if err != nil {
return nil, "", fmt.Errorf("error reading data to sign: %s", err)
}
hash := hasher.Sum(nil)
signature, err = rsa.SignPKCS1v15(rand.Reader, k.PrivateKey, sigAlg.HashID(), hash)
if err != nil {
return nil, "", fmt.Errorf("error producing signature: %s", err)
}
alg = sigAlg.HeaderParam()
return
}
// CryptoPrivateKey returns the internal object which can be used as a
// crypto.PublicKey for use with other standard library operations. The type
// is either *rsa.PublicKey or *ecdsa.PublicKey
func (k *rsaPrivateKey) CryptoPrivateKey() crypto.PrivateKey {
return k.PrivateKey
}
func (k *rsaPrivateKey) toMap() map[string]interface{} {
k.Precompute() // Make sure the precomputed values are stored.
jwk := k.rsaPublicKey.toMap()
jwk["d"] = joseBase64UrlEncode(k.D.Bytes())
jwk["p"] = joseBase64UrlEncode(k.Primes[0].Bytes())
jwk["q"] = joseBase64UrlEncode(k.Primes[1].Bytes())
jwk["dp"] = joseBase64UrlEncode(k.Precomputed.Dp.Bytes())
jwk["dq"] = joseBase64UrlEncode(k.Precomputed.Dq.Bytes())
jwk["qi"] = joseBase64UrlEncode(k.Precomputed.Qinv.Bytes())
otherPrimes := k.Primes[2:]
if len(otherPrimes) > 0 {
otherPrimesInfo := make([]interface{}, len(otherPrimes))
for i, r := range otherPrimes {
otherPrimeInfo := make(map[string]string, 3)
otherPrimeInfo["r"] = joseBase64UrlEncode(r.Bytes())
crtVal := k.Precomputed.CRTValues[i]
otherPrimeInfo["d"] = joseBase64UrlEncode(crtVal.Exp.Bytes())
otherPrimeInfo["t"] = joseBase64UrlEncode(crtVal.Coeff.Bytes())
otherPrimesInfo[i] = otherPrimeInfo
}
jwk["oth"] = otherPrimesInfo
}
return jwk
}
// MarshalJSON serializes this Private Key using the JWK JSON serialization format for
// RSA keys.
func (k *rsaPrivateKey) MarshalJSON() (data []byte, err error) {
return json.Marshal(k.toMap())
}
// PEMBlock serializes this Private Key to DER-encoded PKIX format.
func (k *rsaPrivateKey) PEMBlock() (*pem.Block, error) {
derBytes := x509.MarshalPKCS1PrivateKey(k.PrivateKey)
k.extended["keyID"] = k.KeyID() // For display purposes.
return createPemBlock("RSA PRIVATE KEY", derBytes, k.extended)
}
func rsaPrivateKeyFromMap(jwk map[string]interface{}) (*rsaPrivateKey, error) {
// The JWA spec for RSA Private Keys (draft rfc section 5.3.2) states that
// only the private key exponent 'd' is REQUIRED, the others are just for
// signature/decryption optimizations and SHOULD be included when the JWK
// is produced. We MAY choose to accept a JWK which only includes 'd', but
// we're going to go ahead and not choose to accept it without the extra
// fields. Only the 'oth' field will be optional (for multi-prime keys).
privateExponent, err := parseRSAPrivateKeyParamFromMap(jwk, "d")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key exponent: %s", err)
}
firstPrimeFactor, err := parseRSAPrivateKeyParamFromMap(jwk, "p")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
}
secondPrimeFactor, err := parseRSAPrivateKeyParamFromMap(jwk, "q")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
}
firstFactorCRT, err := parseRSAPrivateKeyParamFromMap(jwk, "dp")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
}
secondFactorCRT, err := parseRSAPrivateKeyParamFromMap(jwk, "dq")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
}
crtCoeff, err := parseRSAPrivateKeyParamFromMap(jwk, "qi")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key CRT coefficient: %s", err)
}
var oth interface{}
if _, ok := jwk["oth"]; ok {
oth = jwk["oth"]
delete(jwk, "oth")
}
// JWK key type (kty) has already been determined to be "RSA".
// Need to extract the public key information, then extract the private
// key values.
publicKey, err := rsaPublicKeyFromMap(jwk)
if err != nil {
return nil, err
}
privateKey := &rsa.PrivateKey{
PublicKey: *publicKey.PublicKey,
D: privateExponent,
Primes: []*big.Int{firstPrimeFactor, secondPrimeFactor},
Precomputed: rsa.PrecomputedValues{
Dp: firstFactorCRT,
Dq: secondFactorCRT,
Qinv: crtCoeff,
},
}
if oth != nil {
// Should be an array of more JSON objects.
otherPrimesInfo, ok := oth.([]interface{})
if !ok {
return nil, errors.New("JWK RSA Private Key: Invalid other primes info: must be an array")
}
numOtherPrimeFactors := len(otherPrimesInfo)
if numOtherPrimeFactors == 0 {
return nil, errors.New("JWK RSA Privake Key: Invalid other primes info: must be absent or non-empty")
}
otherPrimeFactors := make([]*big.Int, numOtherPrimeFactors)
productOfPrimes := new(big.Int).Mul(firstPrimeFactor, secondPrimeFactor)
crtValues := make([]rsa.CRTValue, numOtherPrimeFactors)
for i, val := range otherPrimesInfo {
otherPrimeinfo, ok := val.(map[string]interface{})
if !ok {
return nil, errors.New("JWK RSA Private Key: Invalid other prime info: must be a JSON object")
}
otherPrimeFactor, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "r")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key prime factor: %s", err)
}
otherFactorCRT, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "d")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key CRT exponent: %s", err)
}
otherCrtCoeff, err := parseRSAPrivateKeyParamFromMap(otherPrimeinfo, "t")
if err != nil {
return nil, fmt.Errorf("JWK RSA Private Key CRT coefficient: %s", err)
}
crtValue := crtValues[i]
crtValue.Exp = otherFactorCRT
crtValue.Coeff = otherCrtCoeff
crtValue.R = productOfPrimes
otherPrimeFactors[i] = otherPrimeFactor
productOfPrimes = new(big.Int).Mul(productOfPrimes, otherPrimeFactor)
}
privateKey.Primes = append(privateKey.Primes, otherPrimeFactors...)
privateKey.Precomputed.CRTValues = crtValues
}
key := &rsaPrivateKey{
rsaPublicKey: *publicKey,
PrivateKey: privateKey,
}
return key, nil
}
/*
* Key Generation Functions.
*/
func generateRSAPrivateKey(bits int) (k *rsaPrivateKey, err error) {
k = new(rsaPrivateKey)
k.PrivateKey, err = rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, err
}
k.rsaPublicKey.PublicKey = &k.PrivateKey.PublicKey
k.extended = make(map[string]interface{})
return
}
// GenerateRSA2048PrivateKey generates a key pair using 2048-bit RSA.
func GenerateRSA2048PrivateKey() (PrivateKey, error) {
k, err := generateRSAPrivateKey(2048)
if err != nil {
return nil, fmt.Errorf("error generating RSA 2048-bit key: %s", err)
}
return k, nil
}
// GenerateRSA3072PrivateKey generates a key pair using 3072-bit RSA.
func GenerateRSA3072PrivateKey() (PrivateKey, error) {
k, err := generateRSAPrivateKey(3072)
if err != nil {
return nil, fmt.Errorf("error generating RSA 3072-bit key: %s", err)
}
return k, nil
}
// GenerateRSA4096PrivateKey generates a key pair using 4096-bit RSA.
func GenerateRSA4096PrivateKey() (PrivateKey, error) {
k, err := generateRSAPrivateKey(4096)
if err != nil {
return nil, fmt.Errorf("error generating RSA 4096-bit key: %s", err)
}
return k, nil
}

@ -1,363 +0,0 @@
package libtrust
import (
"bytes"
"crypto"
"crypto/elliptic"
"crypto/tls"
"crypto/x509"
"encoding/base32"
"encoding/base64"
"encoding/binary"
"encoding/pem"
"errors"
"fmt"
"math/big"
"net/url"
"os"
"path/filepath"
"strings"
"time"
)
// LoadOrCreateTrustKey will load a PrivateKey from the specified path
func LoadOrCreateTrustKey(trustKeyPath string) (PrivateKey, error) {
if err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700); err != nil {
return nil, err
}
trustKey, err := LoadKeyFile(trustKeyPath)
if err == ErrKeyFileDoesNotExist {
trustKey, err = GenerateECP256PrivateKey()
if err != nil {
return nil, fmt.Errorf("error generating key: %s", err)
}
if err := SaveKey(trustKeyPath, trustKey); err != nil {
return nil, fmt.Errorf("error saving key file: %s", err)
}
dir, file := filepath.Split(trustKeyPath)
if err := SavePublicKey(filepath.Join(dir, "public-"+file), trustKey.PublicKey()); err != nil {
return nil, fmt.Errorf("error saving public key file: %s", err)
}
} else if err != nil {
return nil, fmt.Errorf("error loading key file: %s", err)
}
return trustKey, nil
}
// NewIdentityAuthTLSClientConfig returns a tls.Config configured to use identity
// based authentication from the specified dockerUrl, the rootConfigPath and
// the server name to which it is connecting.
// If trustUnknownHosts is true it will automatically add the host to the
// known-hosts.json in rootConfigPath.
func NewIdentityAuthTLSClientConfig(dockerUrl string, trustUnknownHosts bool, rootConfigPath string, serverName string) (*tls.Config, error) {
tlsConfig := newTLSConfig()
trustKeyPath := filepath.Join(rootConfigPath, "key.json")
knownHostsPath := filepath.Join(rootConfigPath, "known-hosts.json")
u, err := url.Parse(dockerUrl)
if err != nil {
return nil, fmt.Errorf("unable to parse machine url")
}
if u.Scheme == "unix" {
return nil, nil
}
addr := u.Host
proto := "tcp"
trustKey, err := LoadOrCreateTrustKey(trustKeyPath)
if err != nil {
return nil, fmt.Errorf("unable to load trust key: %s", err)
}
knownHosts, err := LoadKeySetFile(knownHostsPath)
if err != nil {
return nil, fmt.Errorf("could not load trusted hosts file: %s", err)
}
allowedHosts, err := FilterByHosts(knownHosts, addr, false)
if err != nil {
return nil, fmt.Errorf("error filtering hosts: %s", err)
}
certPool, err := GenerateCACertPool(trustKey, allowedHosts)
if err != nil {
return nil, fmt.Errorf("Could not create CA pool: %s", err)
}
tlsConfig.ServerName = serverName
tlsConfig.RootCAs = certPool
x509Cert, err := GenerateSelfSignedClientCert(trustKey)
if err != nil {
return nil, fmt.Errorf("certificate generation error: %s", err)
}
tlsConfig.Certificates = []tls.Certificate{{
Certificate: [][]byte{x509Cert.Raw},
PrivateKey: trustKey.CryptoPrivateKey(),
Leaf: x509Cert,
}}
tlsConfig.InsecureSkipVerify = true
testConn, err := tls.Dial(proto, addr, tlsConfig)
if err != nil {
return nil, fmt.Errorf("tls Handshake error: %s", err)
}
opts := x509.VerifyOptions{
Roots: tlsConfig.RootCAs,
CurrentTime: time.Now(),
DNSName: tlsConfig.ServerName,
Intermediates: x509.NewCertPool(),
}
certs := testConn.ConnectionState().PeerCertificates
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
if _, err := certs[0].Verify(opts); err != nil {
if _, ok := err.(x509.UnknownAuthorityError); ok {
if trustUnknownHosts {
pubKey, err := FromCryptoPublicKey(certs[0].PublicKey)
if err != nil {
return nil, fmt.Errorf("error extracting public key from cert: %s", err)
}
pubKey.AddExtendedField("hosts", []string{addr})
if err := AddKeySetFile(knownHostsPath, pubKey); err != nil {
return nil, fmt.Errorf("error adding machine to known hosts: %s", err)
}
} else {
return nil, fmt.Errorf("unable to connect. unknown host: %s", addr)
}
}
}
testConn.Close()
tlsConfig.InsecureSkipVerify = false
return tlsConfig, nil
}
// joseBase64UrlEncode encodes the given data using the standard base64 url
// encoding format but with all trailing '=' characters omitted in accordance
// with the jose specification.
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
func joseBase64UrlEncode(b []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
}
// joseBase64UrlDecode decodes the given string using the standard base64 url
// decoder but first adds the appropriate number of trailing '=' characters in
// accordance with the jose specification.
// http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31#section-2
func joseBase64UrlDecode(s string) ([]byte, error) {
s = strings.Replace(s, "\n", "", -1)
s = strings.Replace(s, " ", "", -1)
switch len(s) % 4 {
case 0:
case 2:
s += "=="
case 3:
s += "="
default:
return nil, errors.New("illegal base64url string")
}
return base64.URLEncoding.DecodeString(s)
}
func keyIDEncode(b []byte) string {
s := strings.TrimRight(base32.StdEncoding.EncodeToString(b), "=")
var buf bytes.Buffer
var i int
for i = 0; i < len(s)/4-1; i++ {
start := i * 4
end := start + 4
buf.WriteString(s[start:end] + ":")
}
buf.WriteString(s[i*4:])
return buf.String()
}
func keyIDFromCryptoKey(pubKey PublicKey) string {
// Generate and return a 'libtrust' fingerprint of the public key.
// For an RSA key this should be:
// SHA256(DER encoded ASN1)
// Then truncated to 240 bits and encoded into 12 base32 groups like so:
// ABCD:EFGH:IJKL:MNOP:QRST:UVWX:YZ23:4567:ABCD:EFGH:IJKL:MNOP
derBytes, err := x509.MarshalPKIXPublicKey(pubKey.CryptoPublicKey())
if err != nil {
return ""
}
hasher := crypto.SHA256.New()
hasher.Write(derBytes)
return keyIDEncode(hasher.Sum(nil)[:30])
}
func stringFromMap(m map[string]interface{}, key string) (string, error) {
val, ok := m[key]
if !ok {
return "", fmt.Errorf("%q value not specified", key)
}
str, ok := val.(string)
if !ok {
return "", fmt.Errorf("%q value must be a string", key)
}
delete(m, key)
return str, nil
}
func parseECCoordinate(cB64Url string, curve elliptic.Curve) (*big.Int, error) {
curveByteLen := (curve.Params().BitSize + 7) >> 3
cBytes, err := joseBase64UrlDecode(cB64Url)
if err != nil {
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
}
cByteLength := len(cBytes)
if cByteLength != curveByteLen {
return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", cByteLength, curveByteLen)
}
return new(big.Int).SetBytes(cBytes), nil
}
func parseECPrivateParam(dB64Url string, curve elliptic.Curve) (*big.Int, error) {
dBytes, err := joseBase64UrlDecode(dB64Url)
if err != nil {
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
}
// The length of this octet string MUST be ceiling(log-base-2(n)/8)
// octets (where n is the order of the curve). This is because the private
// key d must be in the interval [1, n-1] so the bitlength of d should be
// no larger than the bitlength of n-1. The easiest way to find the octet
// length is to take bitlength(n-1), add 7 to force a carry, and shift this
// bit sequence right by 3, which is essentially dividing by 8 and adding
// 1 if there is any remainder. Thus, the private key value d should be
// output to (bitlength(n-1)+7)>>3 octets.
n := curve.Params().N
octetLength := (new(big.Int).Sub(n, big.NewInt(1)).BitLen() + 7) >> 3
dByteLength := len(dBytes)
if dByteLength != octetLength {
return nil, fmt.Errorf("invalid number of octets: got %d, should be %d", dByteLength, octetLength)
}
return new(big.Int).SetBytes(dBytes), nil
}
func parseRSAModulusParam(nB64Url string) (*big.Int, error) {
nBytes, err := joseBase64UrlDecode(nB64Url)
if err != nil {
return nil, fmt.Errorf("invalid base64 URL encoding: %s", err)
}
return new(big.Int).SetBytes(nBytes), nil
}
func serializeRSAPublicExponentParam(e int) []byte {
// We MUST use the minimum number of octets to represent E.
// E is supposed to be 65537 for performance and security reasons
// and is what golang's rsa package generates, but it might be
// different if imported from some other generator.
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(e))
var i int
for i = 0; i < 8; i++ {
if buf[i] != 0 {
break
}
}
return buf[i:]
}
func parseRSAPublicExponentParam(eB64Url string) (int, error) {
eBytes, err := joseBase64UrlDecode(eB64Url)
if err != nil {
return 0, fmt.Errorf("invalid base64 URL encoding: %s", err)
}
// Only the minimum number of bytes were used to represent E, but
// binary.BigEndian.Uint32 expects at least 4 bytes, so we need
// to add zero padding if necassary.
byteLen := len(eBytes)
buf := make([]byte, 4-byteLen, 4)
eBytes = append(buf, eBytes...)
return int(binary.BigEndian.Uint32(eBytes)), nil
}
func parseRSAPrivateKeyParamFromMap(m map[string]interface{}, key string) (*big.Int, error) {
b64Url, err := stringFromMap(m, key)
if err != nil {
return nil, err
}
paramBytes, err := joseBase64UrlDecode(b64Url)
if err != nil {
return nil, fmt.Errorf("invaled base64 URL encoding: %s", err)
}
return new(big.Int).SetBytes(paramBytes), nil
}
func createPemBlock(name string, derBytes []byte, headers map[string]interface{}) (*pem.Block, error) {
pemBlock := &pem.Block{Type: name, Bytes: derBytes, Headers: map[string]string{}}
for k, v := range headers {
switch val := v.(type) {
case string:
pemBlock.Headers[k] = val
case []string:
if k == "hosts" {
pemBlock.Headers[k] = strings.Join(val, ",")
} else {
// Return error, non-encodable type
}
default:
// Return error, non-encodable type
}
}
return pemBlock, nil
}
func pubKeyFromPEMBlock(pemBlock *pem.Block) (PublicKey, error) {
cryptoPublicKey, err := x509.ParsePKIXPublicKey(pemBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("unable to decode Public Key PEM data: %s", err)
}
pubKey, err := FromCryptoPublicKey(cryptoPublicKey)
if err != nil {
return nil, err
}
addPEMHeadersToKey(pemBlock, pubKey)
return pubKey, nil
}
func addPEMHeadersToKey(pemBlock *pem.Block, pubKey PublicKey) {
for key, value := range pemBlock.Headers {
var safeVal interface{}
if key == "hosts" {
safeVal = strings.Split(value, ",")
} else {
safeVal = value
}
pubKey.AddExtendedField(key, safeVal)
}
}

@ -1,26 +0,0 @@
Copyright (c) 2015, Salesforce.com, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Salesforce.com nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@ -1,151 +0,0 @@
package registry
import (
"net/http"
"strings"
)
// Octet types from RFC 2616.
type octetType byte
// AuthorizationChallenge carries information
// from a WWW-Authenticate response header.
type AuthorizationChallenge struct {
Scheme string
Parameters map[string]string
}
var octetTypes [256]octetType
const (
isToken octetType = 1 << iota
isSpace
)
//nolint:gochecknoinits
func init() {
// OCTET = <any 8-bit sequence of data>
// CHAR = <any US-ASCII character (octets 0 - 127)>
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
// CR = <US-ASCII CR, carriage return (13)>
// LF = <US-ASCII LF, linefeed (10)>
// SP = <US-ASCII SP, space (32)>
// HT = <US-ASCII HT, horizontal-tab (9)>
// <"> = <US-ASCII double-quote mark (34)>
// CRLF = CR LF
// LWS = [CRLF] 1*( SP | HT )
// TEXT = <any OCTET except CTLs, but including LWS>
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
// token = 1*<any CHAR except CTLs or separators>
// qdtext = <any TEXT except <">>
for c := 0; c < 256; c++ {
var t octetType
isCtl := c <= 31 || c == 127
isChar := 0 <= c && c <= 127
isSeparator := strings.ContainsRune(" \t\"(),/:;<=>?@[]\\{}", rune(c))
if strings.ContainsRune(" \t\r\n", rune(c)) {
t |= isSpace
}
if isChar && !isCtl && !isSeparator {
t |= isToken
}
octetTypes[c] = t
}
}
func parseAuthHeader(header http.Header) []*AuthorizationChallenge {
var challenges []*AuthorizationChallenge
for _, h := range header[http.CanonicalHeaderKey("WWW-Authenticate")] {
v, p := parseValueAndParams(h)
if v != "" {
challenges = append(challenges, &AuthorizationChallenge{Scheme: v, Parameters: p})
}
}
return challenges
}
func parseValueAndParams(header string) (value string, params map[string]string) {
params = make(map[string]string)
value, s := expectToken(header)
if value == "" {
return
}
value = strings.ToLower(value)
s = "," + skipSpace(s)
for strings.HasPrefix(s, ",") {
var pkey string
pkey, s = expectToken(skipSpace(s[1:]))
if pkey == "" {
return
}
if !strings.HasPrefix(s, "=") {
return
}
var pvalue string
pvalue, s = expectTokenOrQuoted(s[1:])
if pvalue == "" {
return
}
pkey = strings.ToLower(pkey)
params[pkey] = pvalue
s = skipSpace(s)
}
return
}
func skipSpace(s string) (rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isSpace == 0 {
break
}
}
return s[i:]
}
func expectToken(s string) (token, rest string) {
i := 0
for ; i < len(s); i++ {
if octetTypes[s[i]]&isToken == 0 {
break
}
}
return s[:i], s[i:]
}
func expectTokenOrQuoted(s string) (value string, rest string) {
if !strings.HasPrefix(s, "\"") {
return expectToken(s)
}
s = s[1:]
for i := 0; i < len(s); i++ {
switch s[i] {
case '"':
return s[:i], s[i+1:]
case '\\':
p := make([]byte, len(s)-1)
j := copy(p, s[:i])
escape := true
for i += i; i < len(s); i++ {
b := s[i]
switch {
case escape:
escape = false
p[j] = b
j++
case b == '\\':
escape = true
case b == '"':
return string(p[:j]), s[i+1:]
default:
p[j] = b
j++
}
}
return "", ""
}
}
return "", ""
}

@ -1,23 +0,0 @@
package registry
import (
"net/http"
"strings"
)
type BasicTransport struct {
Transport http.RoundTripper
URL string
Username string
Password string
}
func (t *BasicTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if strings.HasPrefix(req.URL.String(), t.URL) {
if t.Username != "" || t.Password != "" {
req.SetBasicAuth(t.Username, t.Password)
}
}
resp, err := t.Transport.RoundTrip(req)
return resp, err
}

@ -1,108 +0,0 @@
package registry
import (
"io"
"net/http"
"net/url"
"github.com/docker/distribution"
digest "github.com/opencontainers/go-digest"
)
func (registry *Registry) DownloadBlob(repository string, digest digest.Digest) (io.ReadCloser, error) {
url := registry.url("/v2/%s/blobs/%s", repository, digest)
registry.Logf("registry.blob.download url=%s repository=%s digest=%s", url, repository, digest)
resp, err := registry.Client.Get(url)
if err != nil {
return nil, err
}
return resp.Body, nil
}
func (registry *Registry) UploadBlob(repository string, digest digest.Digest, content io.Reader) error {
uploadURL, err := registry.initiateUpload(repository)
if err != nil {
return err
}
q := uploadURL.Query()
q.Set("digest", digest.String())
uploadURL.RawQuery = q.Encode()
registry.Logf("registry.blob.upload url=%s repository=%s digest=%s", uploadURL, repository, digest)
upload, err := http.NewRequest("PUT", uploadURL.String(), content)
if err != nil {
return err
}
upload.Header.Set("Content-Type", "application/octet-stream")
_, err = registry.Client.Do(upload)
return err
}
func (registry *Registry) HasBlob(repository string, digest digest.Digest) (bool, error) {
checkURL := registry.url("/v2/%s/blobs/%s", repository, digest)
registry.Logf("registry.blob.check url=%s repository=%s digest=%s", checkURL, repository, digest)
resp, err := registry.Client.Head(checkURL)
if resp != nil {
defer resp.Body.Close()
}
if err == nil {
return resp.StatusCode == http.StatusOK, nil
}
urlErr, ok := err.(*url.Error)
if !ok {
return false, err
}
httpErr, ok := urlErr.Err.(*HTTPStatusError)
if !ok {
return false, err
}
if httpErr.Response.StatusCode == http.StatusNotFound {
return false, nil
}
return false, err
}
func (registry *Registry) BlobMetadata(repository string, digest digest.Digest) (distribution.Descriptor, error) {
checkURL := registry.url("/v2/%s/blobs/%s", repository, digest)
registry.Logf("registry.blob.check url=%s repository=%s digest=%s", checkURL, repository, digest)
resp, err := registry.Client.Head(checkURL)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return distribution.Descriptor{}, err
}
return distribution.Descriptor{
Digest: digest,
Size: resp.ContentLength,
}, nil
}
func (registry *Registry) initiateUpload(repository string) (*url.URL, error) {
initiateURL := registry.url("/v2/%s/blobs/uploads/", repository)
registry.Logf("registry.blob.initiate-upload url=%s repository=%s", initiateURL, repository)
resp, err := registry.Client.Post(initiateURL, "application/octet-stream", nil)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return nil, err
}
location := resp.Header.Get("Location")
locationURL, err := url.Parse(location)
if err != nil {
return nil, err
}
return locationURL, nil
}

@ -1,46 +0,0 @@
package registry
import (
"fmt"
"io/ioutil"
"net/http"
)
type HTTPStatusError struct {
Response *http.Response
// Copied from `Response.Body` to avoid problems with unclosed bodies later.
// Nobody calls `err.Response.Body.Close()`, ever.
Body []byte
}
func (err *HTTPStatusError) Error() string {
return fmt.Sprintf("http: non-successful response (status=%v body=%q)", err.Response.StatusCode, err.Body)
}
var _ error = &HTTPStatusError{}
type ErrorTransport struct {
Transport http.RoundTripper
}
func (t *ErrorTransport) RoundTrip(request *http.Request) (*http.Response, error) {
resp, err := t.Transport.RoundTrip(request)
if err != nil {
return resp, err
}
if resp.StatusCode >= 400 {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("http: failed to read response body (status=%v, err=%q)", resp.StatusCode, err)
}
return nil, &HTTPStatusError{
Response: resp,
Body: body,
}
}
return resp, err
}

@ -1,50 +0,0 @@
package registry
import (
"encoding/json"
"errors"
"net/http"
"regexp"
)
var (
ErrNoMorePages = errors.New("no more pages")
)
// getPaginatedJSON accepts a string and a pointer, and returns the
// next page URL while updating pointed-to variable with a parsed JSON
// value. When there are no more pages it returns `ErrNoMorePages`.
func (registry *Registry) getPaginatedJSON(url string, response interface{}) (string, error) {
resp, err := registry.Client.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
err = decoder.Decode(response)
if err != nil {
return "", err
}
return getNextLink(resp)
}
// Matches an RFC 5988 (https://tools.ietf.org/html/rfc5988#section-5)
// Link header. For example,
//
// <http://registry.example.com/v2/_catalog?n=5&last=tag5>; type="application/json"; rel="next"
//
// The URL is _supposed_ to be wrapped by angle brackets `< ... >`,
// but e.g., quay.io does not include them. Similarly, params like
// `rel="next"` may not have quoted values in the wild.
var nextLinkRE = regexp.MustCompile(`^ *<?([^;>]+)>? *(?:;[^;]*)*; *rel="?next"?(?:;.*)?`)
func getNextLink(resp *http.Response) (string, error) {
for _, link := range resp.Header[http.CanonicalHeaderKey("Link")] {
parts := nextLinkRE.FindStringSubmatch(link)
if parts != nil {
return parts[1], nil
}
}
return "", ErrNoMorePages
}

@ -1,126 +0,0 @@
package registry
import (
"bytes"
"io/ioutil"
"net/http"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
digest "github.com/opencontainers/go-digest"
)
func (registry *Registry) Manifest(repository, reference string) (*schema1.SignedManifest, error) {
url := registry.url("/v2/%s/manifests/%s", repository, reference)
registry.Logf("registry.manifest.get url=%s repository=%s reference=%s", url, repository, reference)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", schema1.MediaTypeManifest)
resp, err := registry.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
signedManifest := &schema1.SignedManifest{}
err = signedManifest.UnmarshalJSON(body)
if err != nil {
return nil, err
}
return signedManifest, nil
}
func (registry *Registry) ManifestV2(repository, reference string) (*schema2.DeserializedManifest, error) {
url := registry.url("/v2/%s/manifests/%s", repository, reference)
registry.Logf("registry.manifest.get url=%s repository=%s reference=%s", url, repository, reference)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", schema2.MediaTypeManifest)
resp, err := registry.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
deserialized := &schema2.DeserializedManifest{}
err = deserialized.UnmarshalJSON(body)
if err != nil {
return nil, err
}
return deserialized, nil
}
func (registry *Registry) ManifestDigest(repository, reference string) (digest.Digest, error) {
url := registry.url("/v2/%s/manifests/%s", repository, reference)
registry.Logf("registry.manifest.head url=%s repository=%s reference=%s", url, repository, reference)
resp, err := registry.Client.Head(url)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return "", err
}
return digest.Parse(resp.Header.Get("Docker-Content-Digest"))
}
func (registry *Registry) DeleteManifest(repository string, digest digest.Digest) error {
url := registry.url("/v2/%s/manifests/%s", repository, digest)
registry.Logf("registry.manifest.delete url=%s repository=%s reference=%s", url, repository, digest)
req, err := http.NewRequest("DELETE", url, nil)
if err != nil {
return err
}
resp, err := registry.Client.Do(req)
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
return err
}
return nil
}
func (registry *Registry) PutManifest(repository, reference string, manifest distribution.Manifest) error {
url := registry.url("/v2/%s/manifests/%s", repository, reference)
registry.Logf("registry.manifest.put url=%s repository=%s reference=%s", url, repository, reference)
mediaType, payload, err := manifest.Payload()
if err != nil {
return err
}
buffer := bytes.NewBuffer(payload)
req, err := http.NewRequest("PUT", url, buffer)
if err != nil {
return err
}
req.Header.Set("Content-Type", mediaType)
resp, err := registry.Client.Do(req)
if resp != nil {
defer resp.Body.Close()
}
return err
}

@ -1,118 +0,0 @@
package registry
import (
"crypto/tls"
"fmt"
"log"
"net/http"
"strings"
)
type LogfCallback func(format string, args ...interface{})
/*
* Discard log messages silently.
*/
func Quiet(format string, args ...interface{}) {
/* discard logs */
}
/*
* Pass log messages along to Go's "log" module.
*/
func Log(format string, args ...interface{}) {
log.Printf(format, args...)
}
type Registry struct {
URL string
Client *http.Client
Logf LogfCallback
}
/*
* Create a new Registry with the given URL and credentials, then Ping()s it
* before returning it to verify that the registry is available.
*
* You can, alternately, construct a Registry manually by populating the fields.
* This passes http.DefaultTransport to WrapTransport when creating the
* http.Client.
*/
func New(registryURL, username, password string) (*Registry, error) {
transport := http.DefaultTransport
return newFromTransport(registryURL, username, password, transport, Log)
}
/*
* Create a new Registry, as with New, using an http.Transport that disables
* SSL certificate verification.
*/
func NewInsecure(registryURL, username, password string) (*Registry, error) {
transport := &http.Transport{
TLSClientConfig: &tls.Config{
// TODO: Why?
InsecureSkipVerify: true, //nolint:gosec
},
}
return newFromTransport(registryURL, username, password, transport, Log)
}
/*
* Given an existing http.RoundTripper such as http.DefaultTransport, build the
* transport stack necessary to authenticate to the Docker registry API. This
* adds in support for OAuth bearer tokens and HTTP Basic auth, and sets up
* error handling this library relies on.
*/
func WrapTransport(transport http.RoundTripper, url, username, password string) http.RoundTripper {
tokenTransport := &TokenTransport{
Transport: transport,
Username: username,
Password: password,
}
basicAuthTransport := &BasicTransport{
Transport: tokenTransport,
URL: url,
Username: username,
Password: password,
}
errorTransport := &ErrorTransport{
Transport: basicAuthTransport,
}
return errorTransport
}
func newFromTransport(registryURL, username, password string, transport http.RoundTripper, logf LogfCallback) (*Registry, error) {
url := strings.TrimSuffix(registryURL, "/")
transport = WrapTransport(transport, url, username, password)
registry := &Registry{
URL: url,
Client: &http.Client{
Transport: transport,
},
Logf: logf,
}
if err := registry.Ping(); err != nil {
return nil, err
}
return registry, nil
}
func (r *Registry) url(pathTemplate string, args ...interface{}) string {
pathSuffix := fmt.Sprintf(pathTemplate, args...)
url := fmt.Sprintf("%s%s", r.URL, pathSuffix)
return url
}
func (r *Registry) Ping() error {
url := r.url("/v2/")
r.Logf("registry.ping url=%s", url)
resp, err := r.Client.Get(url)
if resp != nil {
defer resp.Body.Close()
}
return err
}

@ -1,26 +0,0 @@
package registry
type repositoriesResponse struct {
Repositories []string `json:"repositories"`
}
func (registry *Registry) Repositories() ([]string, error) {
url := registry.url("/v2/_catalog")
repos := make([]string, 0, 10)
var err error //We create this here, otherwise url will be rescoped with :=
var response repositoriesResponse
for {
registry.Logf("registry.repositories url=%s", url)
url, err = registry.getPaginatedJSON(url, &response)
switch err {
case ErrNoMorePages:
repos = append(repos, response.Repositories...)
return repos, nil
case nil:
repos = append(repos, response.Repositories...)
continue
default:
return nil, err
}
}
}

@ -1,25 +0,0 @@
package registry
type tagsResponse struct {
Tags []string `json:"tags"`
}
func (registry *Registry) Tags(repository string) (tags []string, err error) {
url := registry.url("/v2/%s/tags/list", repository)
var response tagsResponse
for {
registry.Logf("registry.tags url=%s repository=%s", url, repository)
url, err = registry.getPaginatedJSON(url, &response)
switch err {
case ErrNoMorePages:
tags = append(tags, response.Tags...)
return tags, nil
case nil:
tags = append(tags, response.Tags...)
continue
default:
return nil, err
}
}
}

@ -1,128 +0,0 @@
package registry
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
)
type TokenTransport struct {
Transport http.RoundTripper
Username string
Password string
}
func (t *TokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
resp, err := t.Transport.RoundTrip(req)
if err != nil {
return resp, err
}
if authService := isTokenDemand(resp); authService != nil {
resp.Body.Close()
resp, err = t.authAndRetry(authService, req)
}
return resp, err
}
type authToken struct {
Token string `json:"token"`
}
func (t *TokenTransport) authAndRetry(authService *authService, req *http.Request) (*http.Response, error) {
token, authResp, err := t.auth(authService)
if err != nil {
return authResp, err
}
retryResp, err := t.retry(req, token)
return retryResp, err
}
func (t *TokenTransport) auth(authService *authService) (string, *http.Response, error) {
authReq, err := authService.Request(t.Username, t.Password)
if err != nil {
return "", nil, err
}
client := http.Client{
Transport: t.Transport,
}
response, err := client.Do(authReq)
if err != nil {
return "", nil, err
}
if response.StatusCode != http.StatusOK {
return "", response, err
}
defer response.Body.Close()
var authToken authToken
decoder := json.NewDecoder(response.Body)
err = decoder.Decode(&authToken)
if err != nil {
return "", nil, err
}
return authToken.Token, nil, nil
}
func (t *TokenTransport) retry(req *http.Request, token string) (*http.Response, error) {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
resp, err := t.Transport.RoundTrip(req)
return resp, err
}
type authService struct {
Realm string
Service string
Scope string
}
func (authService *authService) Request(username, password string) (*http.Request, error) {
url, err := url.Parse(authService.Realm)
if err != nil {
return nil, err
}
q := url.Query()
q.Set("service", authService.Service)
if authService.Scope != "" {
q.Set("scope", authService.Scope)
}
url.RawQuery = q.Encode()
request, err := http.NewRequest("GET", url.String(), nil)
if username != "" || password != "" {
request.SetBasicAuth(username, password)
}
return request, err
}
func isTokenDemand(resp *http.Response) *authService {
if resp == nil {
return nil
}
if resp.StatusCode != http.StatusUnauthorized {
return nil
}
return parseOauthHeader(resp)
}
func parseOauthHeader(resp *http.Response) *authService {
challenges := parseAuthHeader(resp.Header)
for _, challenge := range challenges {
if challenge.Scheme == "bearer" {
return &authService{
Realm: challenge.Parameters["realm"],
Service: challenge.Parameters["service"],
Scope: challenge.Parameters["scope"],
}
}
}
return nil
}

@ -77,7 +77,6 @@ github.com/docker/distribution
github.com/docker/distribution/digestset
github.com/docker/distribution/manifest
github.com/docker/distribution/manifest/manifestlist
github.com/docker/distribution/manifest/schema1
github.com/docker/distribution/manifest/schema2
github.com/docker/distribution/metrics
github.com/docker/distribution/reference
@ -140,9 +139,6 @@ github.com/docker/go-metrics
# github.com/docker/go-units v0.4.0
## explicit
github.com/docker/go-units
# github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7
## explicit
github.com/docker/libtrust
# github.com/fsnotify/fsnotify v1.4.9
## explicit; go 1.13
github.com/fsnotify/fsnotify
@ -196,9 +192,6 @@ github.com/hashicorp/hcl/hcl/token
github.com/hashicorp/hcl/json/parser
github.com/hashicorp/hcl/json/scanner
github.com/hashicorp/hcl/json/token
# github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5
## explicit; go 1.12
github.com/heroku/docker-registry-client/registry
# github.com/imdario/mergo v0.3.12
## explicit; go 1.13
github.com/imdario/mergo

@ -22,10 +22,13 @@ THE SOFTWARE.
package version
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/heroku/docker-registry-client/registry"
l "github.com/rancher/k3d/v5/pkg/logger"
"github.com/rancher/k3d/v5/pkg/types/k3s"
)
// Version is the string that contains version
@ -46,40 +49,43 @@ func GetVersion() string {
}
// GetK3sVersion returns the version string for K3s
func GetK3sVersion(latest bool) string {
if latest {
version, err := fetchLatestK3sVersion()
if err != nil || version == "" {
l.Log().Warnln("Failed to fetch latest K3s version from DockerHub, falling back to hardcoded version.")
return K3sVersion
func GetK3sVersion(channel string) (string, error) {
if channel != "" {
version, err := fetchLatestK3sVersion(channel)
if err != nil {
return "", fmt.Errorf("error getting K3s version for channel %s: %w", channel, err)
}
return version
return version, nil
}
return K3sVersion
return K3sVersion, nil
}
// fetchLatestK3sVersion tries to fetch the latest version of k3s from DockerHub
func fetchLatestK3sVersion() (string, error) {
// TODO: actually fetch the latest image from the k3s channel server
func fetchLatestK3sVersion(channel string) (string, error) {
url := "https://registry-1.docker.io/"
username := "" // anonymous
password := "" // anonymous
repository := "rancher/k3s"
resp, err := http.Get(k3s.K3sChannelServerURL)
if err != nil {
return "", err
}
defer resp.Body.Close()
out := k3s.ChannelServerResponse{}
hub, err := registry.New(url, username, password)
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to create new registry instance from URL '%s': %w", url, err)
return "", fmt.Errorf("error reading channelserver response body: %w", err)
}
tags, err := hub.Tags(repository)
if err != nil || len(tags) == 0 {
return "", fmt.Errorf("failed to list tags from repository with URL '%s': %w", url, err)
if err := json.Unmarshal(body, &out); err != nil {
return "", fmt.Errorf("error unmarshalling channelserver response: %w", err)
}
l.Log().Debugln("Fetched the following tags for rancher/k3s from DockerHub:")
l.Log().Debugln(tags)
for _, c := range out.Channels {
if c.Name == channel {
return strings.ReplaceAll(c.Latest, "+", "-"), err
}
}
return "sampleTag", nil
return "", fmt.Errorf("no latest version found for channel %s (%s)", channel, k3s.K3sChannelServerURL)
}

@ -0,0 +1,40 @@
/*
Copyright © 2020-2021 The k3d Author(s)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package version
import (
"regexp"
"testing"
)
// GetK3sVersion returns the version string for K3s
func TestGetK3sVersion(t *testing.T) {
searchVersion := "v1.20"
latestRegexp := regexp.MustCompile(`v1\.20\..*`)
v, err := GetK3sVersion(searchVersion)
if err != nil {
t.Fatal(err)
}
if !latestRegexp.Match([]byte(v)) {
t.Fatalf("found version %s (searched: %s) does not match respective latest regexp `%s`", v, searchVersion, latestRegexp.String())
}
}
Loading…
Cancel
Save